AQS机制
Posted ljl150
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AQS机制相关的知识,希望对你有一定的参考价值。
1 Lock lock = new ReentrantLock(); //可以是自己实现的Lock接口的实现类,也可以是jdk提供的同步组件
2 lock.lock();//一般不能放到try语句中
3 try {
4 } finally {
5 lock.unlock(); //一般要求放到finally中,确保即使发生异常也能安全释放掉锁
6 }
- 在finally块中释放锁,目的是保证在获取到锁之后,即使发生异常,锁依然能被顺利释放,从而避免死锁情况的发生。
- 不要将获取锁的过程写在try块中。假设放到try中,如果在获取锁时发生了异常,即锁没有被成功获取到,但finally语句中有释放锁的操作,这就会造成死锁,因为根本没有获取到锁,而底下又要求释放锁。如果没有放到try中,当获取锁失败时,代码立即会报异常而终止运行,因此就避免了死锁。
3.相比于synchronized,Lock接口所具备的其他特性
①尝试非阻塞的获取锁tryLock():当前线程尝试获取锁,如果该时刻锁没有被其他线程获取到,就能成功获取并持有锁,接着返回true,如果没有获取到则返回false。
②能被中断的获取锁lockInterruptibly():获取锁的线程能够响应中断。当线程在获取锁定过程中,如果锁被其他线程占用,则线程一直处于休眠状态,直到获取到锁或被其他线程中断才返回。要注意该线程允许其他线程调用Thread.interrupt()方法来中断等待的线程,当线程被中断掉,不会在去获取锁,会抛出interruptedException异常。
③超时的获取锁tryLock(long time, TimeUnit unit):在指定的截止时间获取锁,如果没有获取到锁返回false。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。
用大白话来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
在AQS中维护了一个volatile int state(代表共享资源)和一个FIFO存放被阻塞的线程的同步队列(多线程争用资源被阻塞时会进入此队列)。
其中state可以使用同步器提供的3个方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))来进行操作,因为它们使用CAS操作能够保证状态的改变是安全的。
那AQS的该如何使用呢?
首先,我们需要去继承AbstractQueuedSynchronizer这个类,然后我们根据我们的需求去重写相应的方法,比如要实现一个独占锁,那就去重写tryAcquire,tryRelease方法,要实现共享锁,就去重写tryAcquireShared,tryReleaseShared;最后,在我们的组件中调用AQS中的模板方法就可以了,而这些模板方法是会调用到我们之前重写的那些方法的。也就是说,我们只需要很小的工作量就可以实现自己的同步组件,重写的那些方法,仅仅是一些简单的对于共享资源state的获取和释放操作,至于像是获取资源失败,线程需要阻塞之类的操作,自然是AQS帮我们完成了。
我们来看看AQS定义的这些可重写的方法:
protected boolean tryAcquire(int arg) : 独占式获取同步状态,试着获取,成功返回true,反之为false
protected boolean tryRelease(int arg) :独占式释放同步状态,等待中的其他线程此时将有机会获取到同步状态;
protected int tryAcquireShared(int arg) :共享式获取同步状态,返回值大于等于0,代表获取成功;反之获取失败;
protected boolean tryReleaseShared(int arg) :共享式释放同步状态,成功为true,失败为false
protected boolean isHeldExclusively() : 是否在独占模式下被线程占用。
接下来我们举一个自定义实现锁的实例的代码:
1 package juc; 2 import java.util.concurrent.locks.AbstractQueuedSynchronizer; 3 //Mutex是我们自定的锁 4 public class Mutex implements java.io.Serializable { 5 //静态内部类,继承AQS 6 private static class Sync extends AbstractQueuedSynchronizer { 7 //是否处于占用状态 8 protected boolean isHeldExclusively() { 9 return getState() == 1; 10 } 11 //当状态为0的时候获取锁,CAS操作成功,则state状态为1, 12 public boolean tryAcquire(int acquires) { 13 if (compareAndSetState(0, 1)) { 14 setExclusiveOwnerThread(Thread.currentThread()); 15 return true; 16 } 17 return false; 18 } 19 //释放锁,将同步状态置为0 20 protected boolean tryRelease(int releases) { 21 if (getState() == 0) throw new IllegalMonitorStateException(); 22 setExclusiveOwnerThread(null); 23 setState(0); 24 return true; 25 } 26 } 27 //同步对象完成一系列复杂的操作,我们仅需指向它即可 28 private final Sync sync = new Sync(); 29 //加锁操作,代理到acquire(模板方法)上就行,acquire会调用我们重写的tryAcquire方法 30 public void lock() { 31 sync.acquire(1); 32 } 33 public boolean tryLock() { 34 return sync.tryAcquire(1); 35 } 36 //释放锁,代理到release(模板方法)上就行,release会调用我们重写的tryRelease方法。 37 public void unlock() { 38 sync.release(1); 39 } 40 public boolean isLocked() { 41 return sync.isHeldExclusively(); 42 } 43 }
上面是锁的实现,其使用的方法和ReentrantLock的使用方法一样,因为ReentrantLock也是基于AQS实现的。
通过前面介绍AQS的框架和使用方法,我们知道它是基于同步对列和state变量实现的,使用同步队列来存放被阻塞的线程。接下来就是介绍它是怎样运用同步队列的?
static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; volatile int waitStatus;//等待状态 volatile Node prev;//指向前一个结点的指针 volatile Node next;//指向后一个节点的指针 volatile Thread thread;//当前结点代表的状态 Node nextWaiter;
前面我们提到过,AQS维护一个共享资源state,通过内置的FIFO来完成获取资源线程的排队工作。(这个内置的同步队列称为"CLH"队列)。该队列由一个一个的Node结点组成,每个Node结点维护一个prev引用和next引用,分别指向自己的前驱和后继结点。AQS维护两个指针,分别指向队列头部head和尾部tail。注意队列中的第一个元素表示正在使用锁的线程,而队列中第二个结点才是第一个真正排队的结点,同步队列的基本结构如图所示。
其实就是个双端双向链表。
为了接下来能够更好的理解加锁和解锁过程的源码,对该同步队列的特性进行简单的讲解:
- 1.同步队列是个先进先出(FIFO)队列,获取锁失败的线程将构造结点并加入队列的尾部,并阻塞自己。如何才能线程安全的实现入队是后面讲解的重点,毕竟我们在讲锁的实现,这部分代码肯定是不能用锁的。
- 2.队列首结点可以用来表示当前正获取锁的线程。
- 3.当前线程释放锁后将尝试唤醒后续处结点中处于阻塞状态的线程。
3.AQS的底层源码分析
之前看的这篇博客感觉写的不错,在这里就直接引用下:https://blog.csdn.net/java_lyvee/article/details/98966684
下面是我根据博客梳理的AQS的tryAcquire()的执行过程图:
https://www.cnblogs.com/chengxiao/p/7141160.html
以上是关于AQS机制的主要内容,如果未能解决你的问题,请参考以下文章