并发专题阶段性总结2
2023-03-20 22:07:51 0 举报
AI智能生成
并发专题
作者其他创作
大纲/内容
Semaphore信号量
作用是控制访问特定资源的线程数目
使用场景
资源访问,服务限流(Hystrix里限流就有基于信号量方式)。
可以用于做流量控制,特别是公用资源有限的应用场景
构造方法
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
permits 表示许可线程的数量
fair 表示公平性
如果这个设为 true 的话,下次执行的线程会是等待最久的线程
核心方法
public void acquire()
acquire() 表示阻塞并获取许可
public void release()
release() 表示释放许可
tryAcquire(long timeout, TimeUnit unit)
方法在没有许可的情况下会立即返回 false,要获取许可的线程不会阻塞
ReentrantReadWriteLock
读写锁介绍
场景
对共享资源有读和写的操作,且写操作没有读操作那么频繁(读多写少
在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量
读锁
读读可以并发
在没有写操作的时候,多个线程同时读一个资源没有任何问题
允许多个线程同时读取共享资源
条件
没有其他线程的写锁
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
写锁
读写,写读,写写互
一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写操作了
条件
没有其他线程的读锁
没有其他线程的写锁
重要特性
公平选择性
非公平
默认
吞吐量还是非公平优于公平
公平
可重入
锁降级
写锁能够降级成为读锁
源码分析
适合读多写少的场景
读写锁接口ReadWriteLock
维护了一对相关的锁
一个用于只读操作readLock()
一个用于写入操作writeLock()
写锁是独占的,读锁是共享的
public ReentrantReadWriteLock(boolean fair)
默认构造方法为** 非公平模式 ,开发者也可以通过指定fair为true设置为 公平模式 **
state变量
独占性和重入性都是通过CAS操作维护AQS内部的state变量实现的
int型
高16位
高16位表示当前读锁的占有量
低16位
低16位表示写锁的占有量
读锁分析
state的高16位等于0
表示当前读锁未被占有
state的高16位大于0
表示当前读锁可能被一个或多个线程占有,多于一个占有读锁的线程,允许重入。
获取条件
当前的写锁未被占有(AQS state变量低16位为0) 或者当前线程是写锁占有的线程
读锁与写锁互斥
除非当前申请读操作的线程是占有写锁的线程,即实现了写锁降级为读锁
readerShouldBlock()方法返回false
非公平模式下AQS的锁等待队列head节点后的节点非共享节点(等待读锁的节点),将返回true
在公平模式下只要AQS锁等待队列的头尾不为空,并且存在head后的节点并且节点的线程非当前线程,返回true。
当前读锁占有量小于最大值(2^16 -1)
保证读锁的占有数不超过最大上限
成功通过CAS操作将读锁占有量+1(AQS的state高16位同步加1)
保证多线程竞争读锁时的安全性
读锁释放
读锁的释放过程即AQS的state高16位同步递减为0的过程
如果读锁的占有数不为0,表示读锁未完全释放。或者写锁的占有数不为0,表示释放的读锁是写锁降级来的
写锁分析
state低16位为0
表示当前写锁没有被占有
反之表示写锁被某个写线程占有(state = 1)或重入(state > 1)
获取条件
读锁未被占用(AQS state高16位为0) ,写锁未被占用(state低16位为0)或者占用写锁的线程是当前线程
写锁与读锁互斥,ReentrantReadWriteLock并没有读锁升级的功能
writerShouldBlock()方法返回false,即不阻塞写线程
非公平模式下非公平模式下允许满足条件的写操作直接插队
在公平模式下AQS的锁等待队列不为空,写操作无法插队
当前写锁占有量小于最大值(2^16 -1),否则抛出Error("Maximum lock count exceeded")
保证读锁的占有数不超过最大上限
通过CAS竞争将写锁状态+1(将state低16位同步+1)
保证多线程竞争读锁时的安全性
写锁释放
写锁的释放过程即AQS的state低16位同步递减为0的过程
如果该写锁占有线程未释放写锁前还占用了读锁,那么写锁释放后该线程就完全转换成了读锁的持有线程
阻塞队列
介绍
队列类型
无限队列unbounded queue,几乎可以无限增长
有限队列bounded queue,定义了最大容量
数据结构
数组
链表
队列具备FIFO先进先出的特性,当然也有双端队列(Deque)优先级队列
常见的阻塞队列
ArrayBlockingQueue 由数组支持的有界队列
LinkedBlockingQueue 由链接节点支持的可选有界队列
PriorityBlockingQueue 由优先级堆支持的无界优先级队列
DelayQueue 由优先级堆支持的、基于时间的调度队列
BlockingQueue API
添加元素
检索元素
阻塞队列特性
阻塞
阻塞功能使得生产者和消费者两端的能力得以平衡,当有任何一端速度过快时,阻塞队列便会把过快的速度给降下来
是否有界
容量的大小,分为有界和无界两种
队列详解
ArrayBlockingQueue
队列基于数组实现,容量大小在创建ArrayBlockingQueue对象时已定义好
队列创建
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>();
应用场景
在线程池中有比较多的应用,生产者消费者场景
生产速度和消费速度基本匹配的情况下
工作原理
基于ReentrantLock保证线程安全,根据Condition实现队列满时的阻塞
一把锁,两个条件
数据结构--数组
初始化时需要指定容量大小,没有扩容机制
没有元素的位置也占用空间被null 占用个
阻塞对象
notEmpty
出队队列count=0,无元素可取时,阻塞在该对象上
notFull
出队队列count=length,放不进去元素时,阻塞在该对象上
设计精髓
使用的数组是个环形数组,这样就减少了对数组删除的时间复杂对,增大了率用率和效率
LinkedBlockingQueue
是一个基于链表的无界队列(理论上有界,内存可能会填满,然后就会 OutOfMemory 异常)
该阻塞队列的大小为Integer.MAX_VALUE
队列创建
BlockingQueue<String> blockingQueue = new LinkedlockingQueue<>();
blockingQueue 的容量将设置为 Integer.MAX_VALUE
工作原理
锁分离
存取互不干扰,存取操作是不同的node对象
takeLock
取Node节点保证前驱后继不会乱
putLock
取Node节点保证前驱后继不会乱
一把锁,两个条件
数据结构--链表
可以指定容量,该阻塞队列的大小为Integer.MAX_VALUE
阻塞对象
notEmpty
出队队列count=0,无元素可取时,阻塞在该对象上
notFull
出队队列count=length,放不进去元素时,阻塞在该对象上
设计精髓
LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,以此来提高整个队列的并发性能
AQS应用
特点介绍
AQS具备特性
阻塞等待队列
共享/独占
公平/非公平
可重入
允许中断
AQS内部维护属性volatile int state (32位)
state表示资源的可用状态
三种访问方式
getState()
setState()
compareAndSetState()
AQS定义两种资源共享方式
Exclusive-独占
只有一个线程能执行,如ReentrantLock
tryAcquire(int)
尝试获取资源,成功则返回true,失败则返回false
tryRelease(int)
尝试释放资源,成功则返回true,失败则返回false
Share-共享
多个线程可以同时执行,如Semaphore/CountDownLatch
tryAcquireShared(int)
尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
tryReleaseShared(int)
尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false
AQS定义两种队列
同步等待队列
双向链表数据结构的队列
FIFO先入先出线程等待队列
线程由原自旋机制改为阻塞机制
条件等待队列
Condition是一个多线程间协调通信的工具类
某些线程一起等待某个条件(Condition),只有当该条件具备时,这些等待线程才会被唤醒,从而重新争夺锁
自定义同步器如何实现
需要实现共享资源state的获取与释放方式
具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现
内部定义了一个Sync的内部类,该类继承AbstractQueuedSynchronized,对该抽象类的部分方法做了实现
isHeldExclusively():该线程是否正在独占资源
只有用到condition才需要去实现它
tryAcquire(int):独占尝试获取资源
tryRelease(int):独占尝试释放资源
tryAcquireShared(int):共享尝试获取资源
tryReleaseShared(int):共享尝试释放资源
CyclicBarrier栅栏屏障
让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行
构造方法
public CyclicBarrier(int parties, Runnable barrierAction)
parties
其参数表示屏障拦截的线程数量
使用场景
可以用于多线程计算数据,最后合并计算结果的场景
一个Excel保存了用户所有银行流水,每个Sheet保存一个账户近一年的每笔银行流水算出整个Excel的日均银行流水
CountDownLatch
能够使一个线程等待其他线程完成各自的工作后再执行
使用场景
应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行
Zookeeper分布式锁,Jmeter模拟高并发等
原理
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量
每当一个线程完成了自己的任务后,计数器的值就会减1
当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务
核心方法
CountDownLatch.countDown()
CountDownLatch.await();
调用 await() 方法的线程会被挂起,它会等待直到 count 值为 0 才继续执行
与Thread.join的区别
CountDownLatch允许一个或多个线程等待其他线程完成操作,类似于join但比它更灵活
join() 的实现原理是不停检查join线程是否存活,如果 join 线程存活则让当前线程永远等待
CountDownLatch可以手动控制在n或者一个线程里调用n次countDown()方法使计数器进行减一操作
与CyclicBarrier的区别
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset() 方法重置
CyclicBarrier是通过ReentrantLock的"独占锁"和Conditon来实现一组线程的阻塞唤醒的,而CountDownLatch则是通过AQS的“共享锁”实现
0 条评论
下一页