JAVA-AQS(AbstractQueuedSynchronizer)
AQS是一个用来构建锁和同步器的框架,使用AQS能简单高效的构造处大量应用广泛的同步器,比如我们提到的ReentrantLock,semaphore,其他的诸如ReentrantReaderWriteLock
SynchronousQueue
,FutureTask,都是基于AQS的。当然,我们自己利用AQS也能构造出符合我们需求的同步器。
提供了原子式的管理同步状态,阻塞和唤醒功能。
AQS的一个核心思想是,如果被请求的共享资源空闲,则当前请求资源的线程设置为有效的工作线程,并且共享资源设置为锁定状态。
如果被请求的共享资源被占有,那么就需要一套线程阻塞等待以及被唤醒的锁分配机制,这个机制就是CLH队列锁实现的,即将暂时获取不到的锁线程加入到队列中。
CLH(Craig,Landin and hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在节点之间的关系).AQS是将每条请求共享资源的线程封装成
一个CLH锁队列的一个节点(Node)来实现锁的分配。
AQS原理图:
AQS使用一个int成员变量来表示同步状态,通过内置FIFO队列来获取资源线程的排队工作
AQS使用CAS对该同步状态进行原子操作并实现值的修改
public volatile int state;//共享变量、使用volatile修饰保证现场的可见性。
状态信息通过protected类型的getState(),setState(),compareAndSetState()进行操作。
通过cas操作原子地 将同步状态值设置同步当前同步状态的值。
二、AQS对资源的共享方式
Exclusive(独占):只有一个线程能执行,如ReentrantLock,又可以分为公平锁和非公平锁。
Share(共享):多个线程可以同时执行,如CountDownLatch、semphore、cyclicBarrier
公平锁:按照线程在队列中排队顺序,先到先拿。不会抢占
非公平锁,谁抢到是谁的锁,通过CAS进行抢锁。
三、AQS底层使用了模板方法模式
同步器设计是基于末班方法模式的,如果需要自定义同步器一般是这样:
1、使用者继承AbstractQueuedSynchronizer
并重写指定的方法
2、将AQS组合在自定义同步组件的实现中,并调用起模板方法,而这些模板方法会调用使用者的重写方法。
AQS使用了模板方法模式,。自定义同步器时需要重写AQS提供的几个钩子方法:
protected boolean tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 protected boolean tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 protected int tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 protected boolean tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 protected boolean isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
AQS组件总结
semaphore(信号量)-允许多个线程同时访问:synchronized和reentrantlock都是一次只允许一个线程访问,semaphore(信号量)可以指定多个线程同时访问某个资源
CountDownLatch(倒计时器):CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,可以让某一个线程等待知道倒计时结束。
CyclicBarrier(循环栅栏):CyclicBarrier和CountDownLatch非常相似,它可以实现线程间的技术等待,但是它的功能比Countdownlatch更加复杂和强大。
它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
CyclicBarrier
默认的构造方法是 CyclicBarrier(int parties)
,其参数表示屏障拦截的线程数量,每个线程调用 await()
方法告诉 CyclicBarrier
我已经到达了屏障,然后当前线程被阻塞。
CountDownLatch什么场景使用?
CountDownLatch的作用就是允许count个线程阻塞在一个地方,直至所有线程执行完毕。
项目中有一个多线程读取多个文件处理的场景用到了,读取6个文件,这6个任务都是没有执行顺序依赖的任务,但是我们需要返回给用户将这几个文件的处理结果进行统计整理。
谓词定义了一个线程池和count为6的CountDownLactch对象。使用线程池处理读取任务,每一个线程处理完后将count-1,调用countDownLatch对象的await方法,直到所有文件读取玩后,
才执行后续的逻辑。
ReentrantLock 与 synchronized 区别