Java知识体系Java并发编程进阶,多线程和锁底层原理探究
Posted 未来村村长
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)进程
一个进程是一个程序的一次启动和执行,一个进程一般由程序段、数据段、进程控制块组成:
- 程序段:代码段,需要执行的指令集合
- 数据段:需要操作的数据和程序运行时产生的数据
- 进程控制块:进程的描述信息和控制信息,是进程存在的唯一标志
- 进程的描述信息:进程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;
public interface Runnable
public abstract void run();
(3)使用Callable和FutureTask
在使用Callable和FutureTask之前我们先来看看它们的源码,来认识一下他们。首先是Callable,同样是一个函数式接口,其中的call与run类似,但是其具有返回值,可通过泛型来定义。
package java.util.concurrent;
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方法动态更改。
使用线程池可以降低资源消耗,提高相应速度和线程的管理性。但是线程数配置不合理会适得其反。对于不同的任务类型将配置不同的线程数:
- IO密集型任务:IO操作时间较长会导致CPU处于空闲状态,可将核心线程数设置为CPU核数的两倍
- CPU密集型任务:CPU一直在运行,可将核心线程数设置为CPU核数
- 混合型任务:最佳线程数=((线程等待时间+线程CPU
以上是关于Java知识体系Java并发编程进阶,多线程和锁底层原理探究的主要内容,如果未能解决你的问题,请参考以下文章
Java多线程编程--线程安全和锁Synchronized概念
Java 多线程进阶-并发编程 线程组ThreadGroup