Java AQS抽象队列同步器

公平锁就是保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁,而非公平锁则无法提供这个保障。
某个线程尝试获取锁时,先会尝试 CAS ,失败后会把自己放入这个是锁的等待队列。
Java 中的 ReentrantLock 默认的锁策略是非公平锁。传入true构造就是公平锁


 

公平锁和非公平锁只有两处不同:

1.非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
2.非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。


 

AbstractQueuedSynchronizer抽象队列同步器简称AQS

AQS类的结构

​ (1)​ AQS是一个通过内置的FIFO双向队列来完成线程的排队工作(内部通过结点head和tail记录队首和队尾元素,元素的结点类型为Node类型,后面我们会看到Node的具体构造)。

 

 

​ (2)Node

  thread:存放进入AQS队列中的线程引用;

  SHARED表示标记线程是因为获取共享资源失败被阻塞添加到队列中的;

  EXCLUSIVE表示线程因为获取独占资源失败被阻塞添加到队列中的;

  waitStatus表示当前线程的等待状态;

 

​   CANCELLED=1:表示线程因为中断或者等待超时,需要从等待队列中取消等待;

 

​   SIGNAL=-1:当前线程thread1占有锁,队列中的head(仅仅代表头结点,里面没有存放线程引用)的后继结点node1处于等待状态,如果已占有锁的线程thread1释放锁或被CANCEL之后就会通知这个结点node1去获取锁执行;

 

​   CONDITION=-2:表示结点在等待队列中(这里指的是等待在某个lock的condition上,关于Condition的原理下面会写到),当持有锁的线程调用了Condition的signal()方法之后,结点会从该condition的等待队列转移到该lock的同步队列上,去竞争lock。(注意:这里的同步队列就是我们说的AQS维护的FIFO队列,等待队列则是每个condition关联的队列);

 

​   PROPAGTE=-3:表示下一次共享状态获取将会传递给后继结点获取这个共享同步状态;

(3)AQS中维持了一个单一的volatile修饰的状态信息state(AQS通过Unsafe的相关方法,以原子性的方式由线程去获取这个state)。AQS提供了getState()、setState()、compareAndSetState()函数修改值(实际上调用的是unsafe的compareAndSwapInt方法)。


 

非公平锁的加锁流程

​ (1)在初始情况下,还没有多任务来请求竞争这个state,这时候如果第一个线程thread1调用了lock方法请求获得锁,首先会通过CAS的方式将state更新为1,表示自己thread1获得了锁,并将独占锁的线程持有者设置为thread1。

​ (2)另一个线程thread2来尝试或者锁,同样也调用lock方法,尝试通过CAS的方式将state更新为1,但是由于之前已经有线程持有了state,所以thread2这一步CAS失败,就会调用acquire(1)方法(该方法是AQS提供的模板方法,它会调用子类的tryAcquire方法)。非公平锁的实现中,AQS的模板方法acquire(1)就会调用NofairSync的tryAcquire方法,而tryAcquire方法又调用的Sync的nonfairTryAcquire方法

nonfairTryAcquire的流程:

  1、获取当前将要去获取锁的线程(thread2)。

  2、获取当前AQS的state的值。如果此时state的值是0,那么我们就通过CAS操作获取锁,然后设置AQS的线程占有者为thread2。CAS失败,后面第3步的重入逻辑也不会进行

  3、如果当前将要去获取锁的线程等于此时AQS的线程持有者,则此时将state的值加1,这是重入锁的实现方式。

  4、state!=0&&不是持有者,nonfairTryAcquire返回false。

​ (3)上面的thread2加锁失败,返回false。那么根据开始我们讲到的AQS概述就应该将thread2构造为一个Node结点加入同步队列中。(哨兵Node)


非公平锁的释放流程

释放

​ (1)unlock方法实际上是调用AQS的release()方法,其中传递的参数为1,表示每一次调用unlock方法都是释放所获得的一次state。重入的情况下会多次调用unlock方法,也保证了lock和unlock是成对的

​ (2)release方法首先会调用ReentrantLock的内部类Sync的tryRelease方法。tryRelease做了这些事情。

​ ①获取当前AQS的state,并减去1;

​ ②判断当前线程是否等于AQS的exclusiveOwnerThread,如果不是,就抛IllegalMonitorStateException异常,这就保证了加锁和释放锁必须是同一个线程;

​ ③如果(state-1)的结果不为0,说明锁被重入了,需要多次unlock,这也是lock和unlock成对的原因;

​ ④如果(state-1)等于0,我们就将AQS的ExclusiveOwnerThread设置为null;

​ ⑤如果上述操作成功了,也就是tryRelase方法返回了true;返回false表示需要多次unlock。

获得锁

①第一件事情就是查看自己的前驱结点是不是头结点

​②前驱结点是head结点,就会调用tryAcquire方法尝试获取state,因为thread1已经释放了state,即state=0,所以thread2调用tryAcquire方法时候,以CAS的方式去将state从0更新为1是成功的,所以这个时候thread2就获取到了锁

​③thread2获取state成功,从acquireQueued方法中退出。

 

posted @ 2021-09-28 17:01  忙碌了一整天的L师傅  阅读(74)  评论(0编辑  收藏  举报