Java.util.concurrent(J.U.C)大大提高了并发性能,AQS是JUC的核心,是阻塞式锁相关的同步器工具的框架,是一个主要用来构建锁和同步器的抽象类。全称AbstractQuenedSynchronizer 意思就是抽象队列同步器。
AQS特点
-
用state属性来表示资源的状态(分独步模式和共享模式),子类需要定义如何维护这个状态,控制如果获取锁和释放锁
-
getState-获取state状态
-
setState-设置state状态
-
compareAndSetState-乐观锁机制设置state状态
-
独占模式:只有一个线程能访问资源
-
共享模式:可以允许多个线程访问资源
-
-
提供基于FIFO等待队列,类似Monitor的EntryList
-
条件变量来实现等待,唤醒机制
子类主要实现这样的一些方法(默认抛出UnsupportedOperationException)
-
tryAcquire //尝试获取锁
-
tryRelease //尝试释放锁
-
tryAcquireShared
-
tryReleaseShared
-
isHeldExclusively //是否持有独占锁
获取锁的姿势:
//如果获取锁失败
if(!tryAcquire(arg)){
//入队,可以选择阻塞当前线程
}
释放锁的姿势:
//如果释放锁失成功
if(tryRelease(arg)){
//让阻塞的线程恢复运行
}
AQS原理
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制。
这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中,
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。
CountDownLatch (倒计时器)
用来控制一个或者多个线程等待多个线程。
它默认构造 AQS 的 state
值为 count
,维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。
StampedLock
JDK8加入,进一步优化读性能,在读锁或者写锁的时候都需要配合【戳】使用
不支持条件变量、不支持可重入
//加解读锁
long stamp = lock.readLock();
lock.unlockRead(stamp);
//加解写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
乐观读,StampedLock支持tryOptimisticRead()方法,读取完毕后要再一次进行戳检验,如果校验通过,表示这期间确实没有写操作,数据可以安全使用,若校验没通过需要重新获取读锁确保数据安全
long stamp = lock.tryOptimisticRead();//戳
//验戳
if(!lock.validate(stamp)){
//锁升级
}
private int data;
private final StampedLock lock = new StampedLock();
public DataContainerStamped(int data){
this.data = data;
}
public int read(){
long stamp = lock.tryOptimisticRead();
sleep(1);
//乐观读
if(lock.validate(stamp)){
return data;
}
//锁升级,读锁
try{
stamp = lock.readLock();
sleep(1);
return data;
}finally{
lock.unlockRead(stamp);
}
}
public void write(){
long stamp = lock.writeLock();
try{
sleep(2);
this.data = newData
}finally{
lock.unlockWriter(stamp);
}
}
Semaphore
信号量, 可以控制对互斥资源的访问线程数 ,限制同步队列的容量
public static void main(String[] args) {
//创建semaphore对象
Semaphore semaphore = new Semaphore(3);
//创建线程 同时运行
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(()->{
//获得许可
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(finalI+"running....");
sleep(1);
System.out.println(finalI+ "end....");
} finally {
semaphore.release();
}
}).start();
}
}
CyclicBarrier
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。
CyclicBarrier 和 CountdownLatch 的一个区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障。
CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值,barrierAction 在所有线程都到达屏障的时候会执行一次。