JAVA并发多线程原理详解
2023-07-04 08:53:26 0 举报
AI智能生成
知识要点和原理解析,详细信息和图片均在黄色图标的注释中,鼠标移动到黄色图标上即会显示,图片加载有时较慢。
作者其他创作
大纲/内容
线程基础信息
概念
线程创建方式
推荐使用Runnable
线程五大状态
创建状态
就绪状态
运行状态
阻塞状态
死亡状态
线程操作
yield
线程礼让
Thread方法,释放CPU
join
合并线程(插队)
Thread方法,释放CPU
wait
线程阻塞等待
Object类方法,在同步方法或代码块中使用,无需捕获异常,会释放CPU,需要notify或notifyAll唤醒;
notify/notifyAll
线程唤醒
Object中的方法,在同步方法或代码块中使用,无需捕获异常,唤醒被wait阻塞的线程,和wait配合用于线程间协作;
CPU线程调度
时间片
抢占式
java内存模型JMM
目的
主要是为了规定了线程和内存之间的一些关系
并发三大特性
1、原子性
2、可见性
3、有序性
synchronized如何保证以上三个特性
同步锁
对JMM语义的规定
访问规则
交互流程
volatile变量规则
2种特性
保证变量的可见性
屏蔽指令重排序
无法保证原子性,一般需要配合CAS
单例陷阱——双重检查锁中的指令重排问题
示例
1、没有锁机制,不安全
2、对方法加同步锁,方面层面性能开销大
3、双重检查锁,解决了安全和开销,但可能出现指令重排
问题的根源
双重检查锁问题解决方案
代码加入volatile关键字
ThreadLocal(线程变量)
操作方法
set方法
get方法
讲解:ThreadlocalMap为ThreadLocal的成员变量,但这里它是当前线程t的一个属性,ThreadLocal本身也就是this作为key使用,一个线程可以使用多个ThreadLocal,不同的ThreadLocal会保存在Entry[] table数组中的不同位置。
ThreadLocal弱引用之内存泄漏
ThreadLocal其实是与线程绑定的一个变量
上图中分为栈和堆两部分,程序进入方法时(进栈),会在栈中创建对象的相关引用,方法结束(出栈)后引用会被清除,对象实际存储在堆中,被栈中的数据所引用。
真正产生内存泄露的原因是线程生命周期没结束,线程未销毁导致Entry强引用始终存在。
ThreadLocalMap解决Hash冲突
线性探测法
CAS(比较并交换)
操作原理
缺点
ABA问题
自旋CPU开销较大
多变量共享一致性问题
CLH队列锁(也属于自旋锁)
CAS自旋锁缺点
锁饥饿问题
性能问题
CLH锁解决CAS什么问题
1、避免饥饿问题
首先它将线程组织成一个队列,保证先请求的线程先获得锁,避免了饥饿问题。
2、锁状态去中心化
其次锁状态去中心化,让每个线程在不同的状态变量中自旋,这样当一个线程释放它的锁时,只能使其后续线程的高速缓存失效,缩小了影响范围,从而减少了 CPU 的开销。
CLH锁缺点
每个等待锁的线程一直自旋等待
适应场合
自旋锁适用于锁占用时间短的场合
CLH锁原理
有一个尾节点指针
通过这个尾结点指针来构建等待线程的逻辑队列,当有新的节点加入队列时,尾节点指针会指向这个新加入的节点,并将原本的尾节点变为当前新加入节点的前驱节点。因此能确保线程线程先到先服务的公平性,尾指针可以说是构建逻辑队列的桥梁;此外这个尾节点指针是原子引用类型,避免了多线程并发操作的线程安全性问题。
线程在自己的某个变量上自旋等待
通过等待锁的每个线程在自己的某个变量上自旋等待,这个变量指向自己的前驱节点中的变量,通过不断地自旋,感知到前驱节点的变化后成功获取到锁。
代码解析
获得锁、释放锁过程
优缺点
优点
性能优异,获取和释放锁开销小
公平锁
实现简单,易于理解
扩展性强
缺点
1、有自旋操作,当锁持有时间长时会带来较大的 CPU 开销
2、基本的 CLH 锁功能单一,不改造不能支持复杂的功能
AQS 对 CLH 队列锁的改造
1、扩展每个节点waitStatus的状态(CLH的状态只有true和false)
2、显式的维护前驱节点(CLH没有前驱节点)
3、后继节点以及诸如出队节点显式设为 null 等辅助 GC 的优化
AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)
LockSupport
特点
许可只有一个,不可累加
LockSupport是不可重入的,如果一个线程连续2次调用LockSupport.park(),那么该线程一定会一直阻塞下去
线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException
休眠期间不会释放所持有的锁(与锁无关)
阻塞和唤醒线程原理
每个线程都有一个Parker实例,LockSupport就是通过控制变量_counter来对线程阻塞唤醒进行控制的。原理有点类似于信号量机制。
相关函数
park:线程阻塞
LockSupport.park()
LockSupport.parkNanos(long nanos)
UNSAFE.park(false, nanos);
LockSupport.parkUntil(long deadline)
parkNanos和parkUntil的区别
parkNanos表示等待多长时间,时间单位是纳秒
parkUntil表示等待到什么时间,时间单位是毫秒
扩展
LockSupport.park(Object blocker)
LockSupport.parkNanos(Object blocker, long nanos)
LockSupport.parkUntil(Object blocker, long deadline)
为何增加park(Object blocker)扩展
blocker是用来标识当前线程在等待的对象,该对象主要用于问题排查和系统监控
unpark:线程唤醒
LockSupport.unpark(Thread thread)
连续多次唤醒的效果和一次唤醒是一样
interrupt()与park()
interrupt()被park()阻塞的线程的中断状态为true(不会主动消耗中断状态),并调用unpark,但不会抛出中断异常,如果没有将中断状态清除(设置为false),后续对该线程的park阻塞将都失效
sleep() / wait() / join()调用后一定会消耗掉中断状态,无论interrupt()操作先做还是后做
阻塞队列
BlockingQueue
应用场景
BlockingQueue接口
入队逻辑
出队逻辑
常用API
常见阻塞队列
超时等待原理
ArrayBlockingQueue
ArrayBlockingQueue基础
ArrayBlockingQueue特性
ArrayBlockingQueue适用场景
生产速度和消费速度基本匹配
代码分析
ArrayBlockingQueue-核心属性
put-入队方法
put主要做了几件事情
enqueue主要做了几件事情
await-阻塞
构建条件等待队列如下
take-出队方法
图示
LinkedBlockingQueue
LinkedBlockingQueue类图
构造方法
Node结构
关键属性
队列特征
入队方法-put
enqueue
出队方法- take
dequeue
问题
和ArrayBlockingQueue的异同
ArrayBlockingQueue里插入元素后,是调用的notEmpty.signal(),怎么这里还不一样了?
Condition
工作原理
接口
Condition也必须和Lock一起使用
ReadLock读锁不支持Condition,WriteLock写锁和互斥锁都支持Condition
condition
await实现
signal实现
Synchronized相关
概要
重量级锁膨胀过程
五种类型锁的特点
对象内存布局
Monitor对象
锁优化
加锁解锁流程
锁的核心结构(3个队列一个属性)
加锁解锁关键步骤
我们能做什么
设置notify后入队模式,控制唤醒顺序
对象在内存中的布局
对象内存布局解析
Mark word
五种状态、四个取值
lock与biased_lock结合
堆里new出来的对象
synchronized关键字下的锁状态升级
无锁
偏向锁
例子
无锁、偏向锁的 lock 标志位是一样的,都是 01
线程栈
关联Lock Record
偏向锁状态时Lock Record与对象头关系
偏向锁小结
1、偏向锁的"锁"即是Mark Word
2、撤销操作可能需要在安全点执行,效率比较低
3、偏向锁的重入计数依靠线程栈里Lock Record个数
4、偏向锁撤销失败,最终会升级为轻量级锁
5、偏向锁退出时并没有修改Mark Word,也就是没有释放锁
偏向锁相比轻量级锁的优势
为什么要引入偏向锁
偏向锁的撤销过程
偏向锁使用了一种等到竞争出现才释放偏向锁的机制
轻量级锁
自旋阈值
锁对象和栈帧
栈帧lockRecord
重入锁 - 栈帧lockRecord
轻量级锁小结
1、轻量级锁的"锁"即是Mark Word,修改需要CAS
2、如果初始锁为无锁状态,则每次进入都需要一次CAS尝试修改为轻量级锁,否则判断是否重入
3、如果不满足2的条件,则膨胀为重量级锁
4、轻量级锁退出时即释放锁,变为无锁状态
5、可以看出轻量级锁比较敏感,一旦有线程竞争就会膨胀为重量级锁
部分源码
轻量级锁相比重量级锁的优势
1、每次加锁只需要一次CAS;
2、不需要分配ObjectMonitor对象;
3、线程无需挂起与唤醒;
只有拿到锁的线程才会有释放锁的操作,为什么此处还需要CAS呢?
值得注意的是
重量级锁
Object Monitor区域和工作过程
重量级锁为什么"重"?
偏向、轻量、重量三者不同点
各种锁的优缺点对比
锁优化
锁消除
逃逸和逃逸分析
锁粗化
示例
Monitor对象
ObjectMonitor 的运用
ObjectMonitor类
Monitor结构
膨胀的流程
加锁和解锁
加锁
初次尝试加锁 - ObjectMonitor::enter()
再次尝试加锁
尝试一次
自旋10次
入队后尝试一次
挂起
流程图
双向链表_EntryList、单向链表 _cxq(栈)
解锁
解锁流程 - ObjectMonitor::exit()
可重入锁:_recursions
Object.notify()
当前占有锁的线程释放锁后会唤醒阻塞等待锁的线程
加锁解锁流程图
重量级锁小结
重量级锁的理解核心
流程总结
尝试获得锁时
当线程释放锁时
线程获得锁后调用Object#wait方法
问题
synchronize 底层维护了几个列表存放被阻塞的线程?
为什么ObjectMonitor需要cxq和entryList两个等待队列
cxq队列中等待线程,什么时候会进到EntryList
等待队列中多个线程,唤醒的顺序是什么
偏向锁和轻量级锁下线程是否可以wait和notify
cxq和waitset数据结构有什么区别
cxq是一个双向链表,采用先进后出的策略
waiset是一个回环链表,采用先进先出的策略
为什么调用 Object 的 wait/notify/notifyAll 方法,需要加 synchronized 锁?
notify/notifyAll后的线程和等待队列中线程,谁会优先抢到锁
如果是notify
如果是notifyAll
Synchronized有类似AQS的公平锁/非公平锁逻辑吗
AQS
简介
AQS最核心的数据结构是一个volatile int state 和 一个FIFO线程等待对列。state代表共享资源的数量(可以大于1),如果是互斥访问,一般设置为1,而如果是共享访问,可以设置为N(N为可共享线程的个数);而线程等待队列是一个双向链表(阻塞队列),无法立即获得锁而进入阻塞状态的线程会加入队列的尾部。当然对state以及队列的操作都是采用了volatile + CAS + 自旋的操作方式,采用的是乐观锁的概念。
核心行为
管理同步状态
state,等于0时释放锁
维护同步队列
阻塞和唤醒线程
实现原理:state+CLH带头节点的双向链表
AQS存在头节点head,创建时是空节点,当同步队列中的线程抢到锁,则将其设置为head节点,head节点代表当前占用线程的节点,head节点对应的线程执行完毕时,会释放资源,修改state状态,并唤醒下一节点(head.next)。
两种资源共享方式:独占(Exclusive)、共享(Share)
waitStatus枚举值
自定义同步器
自定义同步器实现时主要实现以下几种方法
isHeldExclusively()
tryAcquire(int)
tryRelease(int)
tryAcquireShared(int)
tryReleaseShared(int)
代码实现(以非公平锁为例)
获取锁的入口:ReentrantLock.lock()
NonfairSync.lock
acquire()
NonfairSync.tryAcquire
nonfairTryAcquire
加锁成功修改owner和state值
addWaiter
enq
acquireQueued
shouldParkAfterFailedAcquire
判断当前节点是否在获取独占操作权失败后进入阻塞状态
为什么检测到0后,一定要设置成SIGNAL,然后继续下一次循环。直接返回true不行吗
parkAndCheckInterrupt
用于让当前节点所代表的线程进入阻塞状态,并监控其是否收到了“中断”信号
有两种途径可以唤醒该线程
退出acquireQueued的条件:线程获得锁,否则阻塞在for循环内,线程被唤醒或中断后,重新进入for循环获取锁
释放锁入口:ReentrantLock.unlock()
release()
tryRelease()
unparkSuccessor()
cancelAcquire(xx)
源码
获取锁、释放锁流程图
可/不可中断的独占锁
不可中断的独占锁
可中断的独占锁
问题
为什么需要selfInterrupt()?
Condition等待队列
ReentrantLock通过Condition实现类似wait、notify功能
主要方法
await
void await() throws InterruptedException
long awaitNanos(long nanosTimeout)
boolean await(long time, TimeUnit unit)throws InterruptedException
boolean awaitUntil(Date deadline) throws InterruptedException
signal
signalAll
与synchronized与wait()和nitofy()/notifyAll()功能比较
需要与显式锁Lock配合使用
进入等待队列后会释放锁和cpu
await提供了比wait更加强大的机制
多路通知
有选择性地通知
支持超时
实现原理
等待队列
特点
调用condition.await方法后线程依次尾插入到等待队列中
等待队列是一个单向队列
多个等待队列
await
源码
addConditionWaiter
fullyRelease
调用release(savedState)
isOnSyncQueue
超时机制的支持
不响应中断的支持
signal/signalAll
signal
doSignal
transferForSignal
signalAll
总体关系
同步队列与等待队列的异同点
Condition.await/Condition.signal 与Object.wait/Object.notify区别
相同点
不同点
ReentrantLock
加锁流程
ReentrantLock 非公平锁的构造
ReentrantLock lock() 默认实现非公平锁
acquire()
tryAcquire部分由组件自己实现,加入同步队列和阻塞由AQS框架统一
tryAcquire(xx)
加锁流程图
非公平锁的释放
tryRelease
方法
lock()
不可被打断
void lockInterruptibly() throws InterruptedException
可以被打断
tryLock()
设置超时时间
void unlock()
newCondition()
ReentrantLock 与synchronized 异同点
相同点
基本数据结构
实现功能
不同点
实现方式
提供给外界功能
性能区别
synchronized 更耗性能??
对比一下synchronized、 ReentrantLock在高并发的场景下如何处理线程的挂起与唤醒
举证一
synchronized
ReentrantLock
由此可以看出,synchronized 与ReentrantLock 底层挂起线程实现方式是一致的
举证二
ReentrantLock
synchronized
通过比对源码分析ReentrantLock 和 synchronized的CAS、线程挂起方式,发现两者底层实现是一致的。
阻塞队列-BlockingQueue
BlockingQueue接口
入队逻辑
offer(E e):队列没满,插入成功返回true;队列已满,不阻塞,直接返回false
offer(E e,long timeout,TimeUnit unit):如果队列满了,设置阻塞等待时间。超时为入队,返回false
put(E e) :队列没满时正常插入,队列已满则阻塞,等待队列空出位置
出队逻辑
poll():如果队列有数据,出队;如果没有数据,返回null(不阻塞)
poll(long timeout, TimeUnit unit):可设置阻塞时间,如果队列没有数据则阻塞,超过阻塞时间,则返回null;
take():队列里有数据会正常取出数据并删除;但是如果队列里无数据,则阻塞,直到队列里有数据;
peek():获取队首元素,但不移除,队列为空则返回null;
常用API总结
抛出异常
add、remove、element
返回结果但不抛出异常
offer、poll、peek
阻塞
put、take
ArrayBlockingQueue
适用场景
生产速度和消费速度基本匹配(当生产方或消费方线程很快,则会导致该部分线程一直阻塞)
结构特点
指定容量[有界队列],不可扩容
存储结构final Object[] items;存储队列内容
一把锁。线程安全由独占锁ReentrantLock保证[入队、出队都由独占锁锁住]
两条指针分别指向消费索引和生产索引
数据结构[环形数组]
核心属性
items
存放元素的数组
takeIndex
获取的指针位置
putIndex
插入的指针位置
count
队列中的元素个数
lock
ReentrantLock内部锁
notEmpty
消费者等待队列
notFull
生产者等待队列
put-入队方法
示意图
take-出队方法
示意图
阻塞时,不会解除锁占用
LinkedBlockingQueue
特点
内部由单链表实现的 可选有界阻塞队列
队列空间不足时
put方法一直阻塞直到有多余空间
队列元素为空时
take方法一直阻塞直到有元素加入
使用非公平锁ReentrantLock进行并发控制
如果不指定容量,默认为Integer.MAX_VALUE,也就是无界队列
阻塞时,不会解除锁占用
出队和入队使用了不同的锁
出队:takeLock
入队:putLock
相关方法
和ArrayBlockingQueue的异同
队列长度不同
存储方式不同
锁不同
ArrayBlockingQueue采用数组存储元素,因此在插入和移除过程中不需要生成额外对象,LinkedBlockingQueue会生成新的Node节点,对gc会有影响;
线程池
优势
降低资源消耗
提高响应速度
提高线程的可管理性
调用execute() 方法添加一个任务时
拒绝策略
AbortPolicy
丢弃任务,抛出运行时异常
CallerRunsPolicy
由提交任务的线程来执行任务
DiscardPolicy
丢弃这个任务,但是不抛异常
DiscardOldestPolicy
从队列中剔除最先进入队列的任务,然后再次提交任务
workQueue缓冲队列
有界的任务队列-ArrayBlockingQueue
无界的任务队列-LinkedBlockingQueue
直接提交的队列-SynchronousQueue
优先任务队列-PriorityBlockingQueue
功能线程池(自动创建)
定长线程池(FixedThreadPool)
线程池数量固定(即定长),阻塞队列无界(LinkedBlockingQueue)
单线程化线程池(SingleThreadExecutor)
只有一个线程,阻塞队列无界(LinkedBlockingQueue)
可缓存线程池(CachedThreadPool)
线程数无限,无核心线程,每个任务都创建新线程,自动回收多余线程,任务队列为不存储元素的阻塞队列(SynchronousQueue)
定时线程池(ScheduledThreadPool )
核心线程数量固定,非核心线程数量无限,任务队列为延时阻塞队列(DelayedWorkQueue)
禁止直接使用Executors创建线程池原因
手动创建(推荐)-原生ThreadPoolExecutor创建线程池
核心参数
corePoolSize(必需)
maximumPoolSize(必需)
keepAliveTime(必需)
unit(必需)
workQueue(必需)
threadFactory(可选)
handler(可选)
关闭线程池
shutdownNow()
shutdown()
isTerminated()
线程实现复用的原理
线程封装
线程在线程池内部其实是被封装成一个Worker对象,Worker继承了AQS,也就是有一定锁的特性。
线程执行
runWorker()方法
这里有个一个细节就是,因为Worker继承了AQS,每次在执行任务之前都会调用Worker的lock方法,执行完任务之后,会调用unlock方法,这样做的目的就可以通过Woker的加锁状态就能判断出当前线程是否正在运行任务。如果想知道线程是否正在运行任务,只需要调用Woker的tryLock方法,根据是否加锁成功就能判断,加锁成功说明当前线程没有加锁,也就没有执行任务了,在调用shutdown方法关闭线程池的时候,就用这种方式来判断线程有没有在执行任务,如果没有的话,来尝试打断没有执行任务的线程。
如何获取任务的以及如何实现超时
getTask()方法
核心代码:Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
线程通讯及安全
线程通讯调度
线程状态转换
调度方法
涉及同步
JVM如何避免不正常地调用
wait() / wait(long timeout)
wait 执行流程
1、使当前执行代码的线程进行等待. (把线程放到等待队列中)
2、释放当前的锁,然后唤醒同步队列里的节点。
4、调用ParkEvent挂起自己
3、当线程被唤醒后(此时线程是在同步队列里),调用ReenterI(xx)竞争锁
结束等待的条件
其他线程调用该对象的 notify 方法
wait 等待时间超时 (wait 方法提供⼀个带有 timeout 参数的版本, 来指定等待时间)
其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常
wait() VS wait(long timeout)
wait(long timeout)
当线程超过设定时间后,会自动恢复执行
使用有参的 wait(long timeout)方法,线程会进入 TIMED_WAITING
wait()
无限期等待
使用无参的 wait() 方法,线程会进入 WAITING
notify()
唤醒等待的线程
方法 notify() 也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁
如果有多个线程等待,则由线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到")
这里的随机也是有规则的
在 notify() 方法后,当前线程不会马上释放该对象锁,要等到执行 notify() 方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁;
notify 操作没有释放锁
源码
1、将节点从等待队列里移出
2、移动到同步队列
notifyAll()
⼀次唤醒所有的等待线程
notifyAll() 并不是唤醒所有 wait 等待的线程,而是唤醒当前对象(lock)处于 wait 等待的所有线程
wait/notify/notifyAll 流程图
问题
线程A成功获取锁后,线程B再次获取锁会失败,线程B该如何自处?
线程A成功获取锁后,调用wait()方法,此时线程A处在什么状态?
线程A退出临界区释放锁后,又做了什么?
线程B调用notify()方法,发生了什么?
notify 唤醒线程是随机的吗?
什么是虚假唤醒?
流程总结
wait VS sleep
wait(0) 与 sleep(0) 的区别
Thread.sleep(0) 表示重新触发一次 CPU 竞争
wait(0) 表示无期限的等待,直到有线程唤醒它为止
wait 和 sleep 释放锁
wait方法在执行的时候都会释放锁
sleep不会释放锁
相同点和异同点
相同点
都可以让线程休眠
都可以响应 interrupt 的响应
不同点
wait 必须在 synchronized 中使⽤,而 sleep 却不⽤
sleep 是 Thread 的⽅法,而 wait 是 Object 的⽅法
sleep 不释放锁,wait 释放锁
sleep 有明确的终止等待时间,而 wait 有可能无限期的等待下去
sleep 和 wait 产生的线程状态是不同的,sleep 是 TIMED_WAITING 状态,而 wait 是 WAITING 状态
yield
让出当前线
调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态(仍然有可能被执行),然后调度执行其它线程
具体的实现依赖于操作系统的任务调度器
join
用于等待某个线程结束。哪个线程内调用join()方法,就等待哪个线程结束,然后再去执行其他线程。
interrupt:线程中断
作用
作用是中断线程。将会设置该线程的中断状态位,即设置为true
如何中断线程
运行状态线程(running或者是runnable两种状态)
不会中断一个正在运行的线程,任务中一般都会有循环结构,只要用一个标记控制住循环
等待状态线程(Object.wait()、Object.wait(long)、Object.wait(long,int)、Thread.join、Thread.join(long,int)、Thread.sleep(long,int))
中断状态复位
1、interrupted()
测试当前线程是否已经中断(静态方法),具有清除状态的功能
2、异常使线程复位
代码示例
为什么要复位
isInterrupted()
测试线程是否已经中断,但是不能清除状态标识
线程的终止原理
源码
对线程中断的反应
RUNNABLE
如果线程在运行中,interrupt()只是会设置线程的中断标志位,没有任何其它作用。线程应该在运行过程中合适的位置检查中断标志位,比如说,如果主体代码是一个循环,可以在循环开始处进行检查
WAITING/TIMED_WAITING
会使得该线程抛出InterruptedException,需要注意的是,抛出异常后,中断标志位会被清空(线程的中断标志位会由true重置为false,因为线程为了处理异常已经重新处于就绪状态。),而不是被设置。InterruptedException是一个受检异常,线程必须进行处理。
Object.wait()、Object.wait(long)、Object.wait(long,int)、Thread.join、Thread.join(long,int)、Thread.sleep(long,int)以上阻塞方法被中断时会消耗中断状态
BLOCKED
如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依然会处于BLOCKED状态,也就是说,interrupt()并不能使一个在等待锁的线程真正”中断”。
NEW/TERMINATE
如果线程尚未启动(NEW),或者已经结束(TERMINATED),则调用interrupt()对它没有任何效果,中断标志位也不会被设置。
线程安全
线程安全解决思路
互斥和同步
互斥
如:synchronized、ReentrantLock
同步
如:wait/notify
非阻塞同步
如:CAS指令
存在问题
1.ABA
2.自旋消耗资源
3.多变量共享一致性问题
非阻塞同步对于阻塞同步而言主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题
无需同步
如:ThreadLocal、final
线程间的通信
思路
关键字 volatile
等待、通知机制
Object类的wait()和notify()
wait()/notify()/notifyAll() 必须配合 synchronized 使用,wait 方法释放锁,notify 方法不释放锁。
LockSupport
LockSupport对指定线程阻塞或唤醒,与锁资源无关
Thread.join()
阻塞当前执行线程,当被join 的线程执行完再重新执行
并发包工具类
CountDownLatch
Condition 结合 ReentrantLock
condition.await()
condition.signal()
唤醒condition条件队列中等待的线程,当前线程并不会释放锁
异步线程
Runnable和Callable
Callable接口
如何使用Callable
Future机制原理
FutureTask继承关系
使用示例
Future源码解析
线程执行状态值
FutureTask源码解析
FutureTask的run方法
set方法
finishCompletion()
FutureTask中的waiters
多个线程等待
get方法
awaitDone
report(int s)
FutureTask的取消
Future的局限性
CompletionService
针对Future的不足
CompletionService接口
ExecutorCompletionService
核心实现
关键属性
重写FutureTask
构造器
执行任务
获取任务返回值
应用场景
1、询价应用:向不同电商平台询价,并保存价格
ThreadPoolExecutor+Future方式
CompletionService方式
2:实现类似 Dubbo 的 Forking Cluster场景
代码模拟实现
应用场景总结
CompletableFuture
CompletableFuture的API
runAsync():异步运行
supplyAsync()
CompletionStage
Funtion方法会产生结果
Comsumer会消耗结果
Runable既不产生结果也不消耗结果
源码分析
CompletableFuture
runAsync()
AsyncRun类
Completion
uniAccept
UniAccept类
uniCompletion
示例代码原理进行分析
示例代码
运作流程
1、主线程调用CompletableFuture的supplyAsync()方法
AsyncSupply实现了Runnable的run()方法
2、main线程会继续执行CompletableFuture的thenAccept
3、回到“源任务”
postComplete
pushStack
使用踩坑
总结
0 条评论
下一页