Java知识体系Java并发编程进阶,多线程和锁底层原理探究

大鹏一日同风起,扶摇直上九万里。这篇文章主要讲述Java知识体系Java并发编程进阶,多线程和锁底层原理探究相关的知识,希望能为你提供帮助。




||To Up||未来村村长正推出一系列【To Up】文章,该系列文章重要是对java开发知识体系的梳理,关注底层原理和知识重点。”天下苦八股文久矣?吾甚哀,若学而作苦,此门无缘,望去之。“该系列与八股文不同,重点在于对知识体系的构建和原理的探究。


文章目录

  • ??||To Up||??
  • ??一、Java多线程??????
  • ??1、进程与线程??
  • ??(1)进程??
  • ??(2)线程??
  • ??(3)线程与进程的区别??
  • ??2、线程的创建??
  • ??(1)继承Thread类??
  • ??(2)实现Runnable接口??
  • ??(3)使用Callable和FutureTask??
  • ??(4)使用线程池创建??
  • ??3、线程的状态与相关操作??
  • ??(1)相关状态与操作??
  • ??(2)守护线程??
  • ??(3)线程组??
  • ??① 创建??
  • ??② 使用??
  • ??③ 线程组的枚举??
  • ??④ main线程组的获取??
  • ??4、线程间通信??
  • ??5、线程池的架构设计??
  • ??(1)线程池架构设计??
  • ??(2)Executors的使用与弊端??
  • ??5、线程池标准创建方式与相关原理??
  • ??(1)ThreadPoolExecutor??
  • ??① 核心和最大线程数量??
  • ??② BlockingQueue??
  • ??③ keepAliveTime??
  • ??(2)向线程池提交任务的两种方式??
  • ??(3)任务调度流程??
  • ??(4)线程工厂ThreadFactory??
  • ??(5)任务阻塞队列??
  • ??(6)线程池的拒绝策略??
  • ??(7)线程池的状态与关闭??
  • ??6、ThreadLocal:线程安全解决??
  • ??(1)ThreadLocal原理??
  • ??(2)与Synchnized区别??
  • ??(3)ThreadLocal成员方法??
  • ??二、Java内置锁??????
  • ??1、synchronized关键字??
  • ??(1)synchronized同步方法??
  • ??(2)synchronized同步块??
  • ??(3)Synchronized的两个推论??
  • ??2、Java内置锁??
  • ??(1)Java对象与内置锁??
  • ??(2)内置锁的状态??
  • ??① 无锁状态??
  • ??② 偏向锁状态??
  • ??③ 轻量级锁状态??
  • ??④ 重量级锁状态??
  • ??3、synchronized执行原理??
  • ??4、CAS??
  • ??(1)原理??
  • ??(2)使用??
  • ??(3)CAS操作的弊端和规避??
  • ??三、JUC显式锁??????
  • ??1、显式锁Lock接口??
  • ??2、可重入锁ReentrantLock??
  • ??3、显式锁的使用??
  • ??(1)使用lock阻塞抢锁??
  • ??(2)使用tryLock非阻塞抢锁??
  • ??(4)使用tryLock限时抢锁??
  • ??4、LockSupport??
  • ??5、锁的类型??
  • ??(1)可重入锁和不可重入锁??
  • ??(2)悲观锁和乐观锁??
  • ??(3)公平锁和非公平锁??
  • ??(4)可中断锁和不可中断锁??
  • ??(5)共享锁和独占锁??
  • ??(6)读写锁??
  • ??四、AQS:显式锁的原理??????
  • ??1、AQS的组成??
  • ??(1)状态标志位??
  • ??(2)队列节点类??
  • ??(3)FIFO双向同步队列??
  • ??(4)钩子方法??
  • ??2、AQS实现锁原理??
  • ??(1)自己实现一个简单锁??
  • ??(2)AQS锁抢占原理??
  • ??① 锁抢占执行过程??
  • ??② acquire??
  • ??③ addWaiter()??
  • ??④ acquireQueued()??
  • ??(3)AQS释放锁原理??
  • ??① 锁释放执行过程??
  • ??② release()??
  • ??② tryRelease()??
  • ??③ unparkSuccessor(h)??
  • ??3、AQS条件队列??
  • ??(1)Condition基本原理??
  • ??(2)await等待方法原理??
  • ??(3)signal唤醒方法原理??
  • ??五、JUC原子类???????
  • ??(1)基本类型原子操作??
  • ??(2)引用类型原子操作??
  • ??六、volatile??????
  • ??1、并发三大问题??
  • ??(1)原子性问题??
  • ??(2)可见性问题??
  • ??(3)有序性问题??
  • ??2、volatile原理??
一、Java多线程???? 1、进程与线程
(1)进程【Java知识体系Java并发编程进阶,多线程和锁底层原理探究】 一个进程是一个程序的一次启动和执行,一个进程一般由程序段、数据段、进程控制块组成:
  • 程序段:代码段,需要执行的指令集合
  • 数据段:需要操作的数据和程序运行时产生的数据
  • 进程控制块:进程的描述信息和控制信息,是进程存在的唯一标志
  • 进程的描述信息:进程ID和进程名称
  • 进程的调度信息:程序的起始地址和通信信息
  • 进程的资源信息:内存信息和文件句柄
  • 进程上下文:CPU寄存器的值、当前程序计数器的值
每当使用Java命令启动一个Java应用程序时,就会启动一个JVM进程,所有的Java程序代码都是以线程运行,JVM找到程序的入口main()方法,然后运行main方法产生一个线程,同时还会启动另外一个GC线程用于垃圾回收。
(2)线程 线程是指“进程代码段”的一次顺序执行流程,线程是CPU调度的最小单位,而进程是操作系统资源分配的最小单位,线程之间共享进程的内存空间、系统资源。
线程的组成如下:
  • 线程ID:线程的唯一标识,同一个进程内的线程ID不会重复
  • 线程名称:用于用户识别,若没有显式命名系统会自动分配
  • 线程优先级:表示线程调度的优先级,优先级越高获得CPU的执行机会越大
  • 线程状态:分别为新建(NEW)、可执行(RUNNABLE)、阻塞(BLOCKED)、无限期等待(WAITING)、限时等待(TIMED_WAITING)、结束(TERMINATED)。
  • 线程类型:是否为守护线程
(3)线程与进程的区别 线程是“进程代码段”的一次顺序执行流程,一个进程由多个线程组成,一个进程至少有一个线程。线程是CPU调度的最小单位,进程是操作系统分配资源的最小单位。进程之间相互独立,但进程内部的各个线程之间并不完全相互独立。各个线程之间共享进程的方法区内存、堆内存以及系统资源。
2、线程的创建
(1)继承Thread类 Thread类是Java多线程编程的基础,通过继承Thread类创建线程类可以实现线程的创建:
  • 继承Thread类,创建新的线程类
  • 重写run()方法,将需要并发执行的业务代码编写在run()方法中
  • 调用Thread实例的start()方法启动线程
  • 线程启动后,线程的run方法将被JVM执行
Thread类的源码量较大,不作展示分析。我们只需要知道Thread定义了线程的状态以及操作线程的相关方法即可。
(2)实现Runnable接口 Thread也实现了Runnable接口,且Thread中有以下构造方法可通过传入Runnable接口实现对象参数来实现线程的创建。
//系统定义名称
public Thread(Runnable target)
init(null, target, "Thread-" + nextThreadNum(), 0);

//自定义名称创建
public Thread(Runnable target, String name)
init(null, target, name, 0);

则通过实现Runnable接口来创建线程的步骤如下:
  • 定义一个类实现Runnable接口
  • 实现Runnable接口中的run()抽象方法【业务处理逻辑】
  • 通过Thread的构造方法创建线程对象,传入Ruunable实例作为参数
  • 调用Thread实例的start()方法启动线程
  • 线程启动后,线程的run方法将被JVM执行,Thread的run方法将会调用Runnable实例的run方法
这里Thread的run()方法先判断target是否为null,这里的target类型就是Runnable,即我们传入的参数。
public void run()
if (target != null)
target.run();


我们也可以看看Runnable的源码,可以看到Runnable是一个函数式接口,即只有一个方法的接口,其与Thread都来自java.lang包。
package java.lang;
@FunctionalInterface
public interface Runnable
public abstract void run();

(3)使用Callable和FutureTask 在使用Callable和FutureTask之前我们先来看看它们的源码,来认识一下他们。首先是Callable,同样是一个函数式接口,其中的call与run类似,但是其具有返回值,可通过泛型来定义。
package java.util.concurrent;
@FunctionalInterface
public interface Callable< V>
V call() throws Exception;

但是我们知道要创建线程离不开Thread类,所以这里使用了FutureTask进行牵线搭桥。我们可以看到FutureTask类的声明和构造器。
public class FutureTask< V> implements RunnableFuture< V>
public FutureTask(Callable< V> callable)
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable


FutureTask继承了RunnableFuture,其源码如下。所以我们可以想到,通过FutureTask构造器可构造一个Runnable实例,这样就可以传入Thread代理执行。
package java.util.concurrent;
public interface RunnableFuture< V> extends Runnable, Future< V>
void run();

使用Callable和FutureTask创建线程的步骤如下:
  • 创建一个Callable接口的实现类,实现call()方法【编写业务逻辑并设置返回值】
  • 使用Callable实现类的实例构造一个FutureTask实例
  • 使用FutureTask实例作为Thread构造器的参数target创建一个线程
  • 调用Thread实例的start()方法启动新线程,Thread的run方法会执行FutureTask的run方法,最终会调用Callable的call方法
(4)使用线程池创建 可以通过Executors工厂类构建线程池,然后通过其execute()【没有返回值,只接收Runnable实例和Thread实例】方法和submit()【可接收有返回值的Callable实例,或Runnable实例和Thread实例】方法实现线程的创建和执行。但生产环境不允许通过Executors创建线程池,需要通过调用ThreadPoolExecutor的构造方法完成。
线程池具体原理与操作后续会进行说明。
3、线程的状态与相关操作
(1)相关状态与操作 在Thread源码中使用enum枚举了Thread的六种状态:
  • NEW(新建):线程创建,未调用start方法启动,这里会调用相应的init方法进行创建【new】
  • RUNNABLE(可运行):线程处于可执行状态,已经在Java虚拟机执行,但还在等其它操作系统资源【start()】
  • 就绪状态:调用start()、CPU时间片用完、sleep()操作结束、join()操作结束、抢到对象锁、调用yield()方法
  • BLOCKED(阻塞):线程处于阻塞,线程在等待一个监控锁,特别是多线程下的场景等待另一个线程同步块的释放。【synchronized】
  • WAITING(等待):线程处于等待状态,指的是该线程正在等待另一个线程执行某些特定的操作【wait()、join()】
  • TIMED_WAITING(调校时间的等待):与时间相关的等待,调用了设定等待时长参数的方法【sleep(xx)、wait(xx)、LockSupport.parkNanos(xx)/parkUntil(xx)】
  • TERMINATED(终止):线程执行完毕的状态或执行过程发生了异常
我们可以通过getState()方法获取线程的执行状态,或者通过isAlice()方法判断一个线程是否还存活。

(2)守护线程 JVM进程中的GC线程就是一个守护线程,守护线程的使用有以下要点:
  • 守护线程必须在启动前通过setDaemon()方法将状态设置为true,启动后就不能进行设置,否则报InterruptedException异常
  • 守护线程存在被JVM强制终止的风险,所以在守护线程中尽量不去访问系统资源
  • 守护线程中创建的线程也是守护线程
(3)线程组一组线程或线程组的集合,在多线程情况下,对线程进行分组管理。直接在main方法中运行的线程或线程组,都属于main线程组,在main方法中运行的代码上一级为System线程组,其中线程的上一级为main线程组。
① 创建
ThreadGroup threadGroup01 = new ThreadGroup()

② 使用
Thread thread01 = new Thread(threadGroup01,new ThreadImplentsRunnable(),"thread-01");
Thread thread02 = new Thread(threadGroup01,new ThreadImplentsRunnable(),"thread-02")

③ 线程组的枚举
Thread[] threadList = new Thread[10];
threadGroup.enumerate(threadList);

④ main线程组的获取
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup()

4、线程间通信
线程的通信可以定义为:当多个线程共同操作共享资源时,线程间通过某种方式互相告知自己的状态,避免无效的资源争夺。通信方式有:等待-通知、共享内存、管道流。其中[等待-通知]是使用较普遍的通信方式。
Java内置锁可以使用wait()和notify()来实现”等待-通知“的通信方式。使用wait()方法以后,JVM会将当前线程加入该锁监视器的等待集合(WaitSet)。使用notify()后,JVM会唤醒该锁监视器等待集合中的第一条线程,若使用notifyAll会唤醒监视器等待集合的所有线程。
5、线程池的架构设计
Java线程的创建和销毁代价都比较高,频繁的创建和销毁线程非常低效,所以出现了线程池。线程池的好处:
  • 提升性能:不需要自己创建线程,将任务交给线程池去执行,线程池能尽可能使用空闲的线程执行异步任务,对创建的线程实现复用
  • 线程管理:线程池可以对线程进行有效管理,使得异步任务得到高效调度执行
(1)线程池架构设计
① Executor:Executor是Java异步目标任务”执行者“接口,其只包含一个方法execute(Runnable command)。
② ExecutorService:ExecutorService继承Executor,其新增了submit和invoke系列方法,对外提供了异步任务的接收服务。
③ AbstractExecutorService:AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。
④ ThreadPoolExecutor:JUC线程池的核心实现类,线程池预先提供了指定数量的可重用线程,并对每个线程池都维护了一些基础数据统计,方便线程的管理和监控。
⑤ ScheduledExecutorService:继承于ExecutorService,用于完成”延时“和周期性任务的调度线程接口。
⑥ ScheduledThreadPoolExecutor:它提供了ScheduledExecutorService中的”延时执行“和”周期执行“等抽象调度方法的具体实现。
⑦ Executors是一个静态工厂类,提供了快速创建线程池的方法。
(2)Executors的使用与弊端 Java通过Executors工厂类提供了4中快捷创建线程池的方法。
方法名
功能简介
newSingleThreadExecutor()
创建只有一个线程的线程池
newFixedThreadPool(int nThreads)
创建固定大小的线程池
newCachedThreadPool()
创建一个不限制线程数量的线程池,任何提交的任务都立即执行,但空闲线程会得到及时回收
newScheduledThreadPool()
创建一个可定期或延时执行任务的线程池
使用Executors工厂类创建线程池有以下潜在问题:
  • 通过newFixedThreadPool(int nThreads)创建固定大小的线程池或通过newSingleThreadExecutor()创建只有一个线程的线程池,若任务提交速度持续大于任务处理速度,会造成大量的任务等待,等待队列过大会造成内存溢出异常
  • 通过newCachedThreadPool()创建一个不限制线程数量的线程池,若大量任务被启动,则需要创建大量的线程,也可能导致内存溢出异常(OOM,Out Of Memory)
  • 通过newScheduledThreadPool()创建一个可定期或延时执行任务的线程池,同样会因为线程数不设限制,从而导致OOM。
5、线程池标准创建方式与相关原理
(1)ThreadPoolExecutor 企业开发规范会要求使用标准的ThreadPoolExecutor构造工作线程池,其中会使用到其较重要的构造器如下:
public ThreadPoolExecutor(
int corePoolSize,//核心线程数,空闲也不会回收
int maximumPoolSize,//最大线程数
long keepAliveTime,TimeUnit unit,//线程最大空闲时长
BlockingQueue< Runnable> workQueue,//任务的排队队列
ThreadFactory threadFactory,//新线程的产生方式
RejectedExecutionHandler handler//拒绝策略
)

① 核心和最大线程数量 线程池执行器根据corePoolSize和maximumPoolSize来自动维护线程池的工作线程,当maximumPoolSize被设置为Integer.MAX_VALUE时,线程池可以接收任意数量的并发任务。corePoolSize和maximumPoolSize可以在运行过程中通过set方法动态更改。
使用线程池可以降低资源消耗,提高相应速度和线程的管理性。但是线程数配置不合理会适得其反。对于不同的任务类型将配置不同的线程数: