网易云课堂微专业|以ReentrantLock为例理解AQS原理


JAVA AQS

  • 前言
  • 什么是AQS
  • AQS的原理
  • ReentrantLock的加锁原理解读
  • 总结

前言 在阅读这篇文章之前,假设你已经知道了下面的概念:
1.线程、多线程。
2.使用多线程引入的,可见性问题、原子性问题。
3.CAS原子操作
4.公平锁、非公平锁
5.独占锁、共享锁
6.可重入锁、不可重入锁
什么是AQS AbstractQueuedSynchronizer,抽象队列同步器。简称AQS。是JDK提供的,位于J.U.C locks包下的一个抽象类,相当于提供了一个模板,实现让线程排队访问资源的功能,保证资源在被操作的时候是线程安全的。 在J.U.C下的许多类,例如ReentrantLock、ReentrantReadWriteLock、CountDownLatch、CyclicBarrier、Semaphore,都是通过继承AQS抽象类,加入自己的特定逻辑、实现自己特有的功能。
AQS的原理 AQS中维护了一个volatile int state,用来代表共享资源。 与此同时还维护了一个FIFO线程等待队列,当多线程争抢资源遇到阻塞(当state大于0的时候,代表当前资源已经被占有,即遇到了阻塞)的时候会进入此队列等待。 使用volatile关键字可以保证多个线程操作state的时候,线程在任何时候都读到的是最新的值。 等待对列中的线程是通过JVM提供的UNSAFE.park()方法,实现对线程的挂起操作。
ReentrantLock的加锁原理解读 网易云课堂微专业|以ReentrantLock为例理解AQS原理
文章图片

如图,有四个线程t1,t2,t3,t4,来对资源进行加锁。
state的初始值是0,当然这个是ReentrantLock或者说AQS对象刚创建好时候state的值,owner就是锁的持有者,初始值是null,队列也是空的。 现在来了4个线程,相当于同时调用了lock方法也就是调用了AQS的acquire(int arg)方法。
然后相当于4个线程同时CAS(0,1)操作,state的值。 我们知道CAS操作是原子操作,多次相同的CAS只有一次可以成功。我们假设T1线程成功了。请看下图。
网易云课堂微专业|以ReentrantLock为例理解AQS原理
文章图片

可以看到,state的状态被改为了1,同时呢owner被改为了t1线程。 也就是说,其他的3个线程CAS操作失败了,那么失败了怎么办呢,答案是进入等待队列,同时挂起线程,也就是调用JAVA UnSafe.park(); 方法。如下图所示:
网易云课堂微专业|以ReentrantLock为例理解AQS原理
文章图片

现在还面临一个问题,t1线程又做了一次加锁操作,按照我们刚才的逻辑,这次CAS必然会导致失败,那么T2线程是不是也要进入队列中把自己挂起呢,不是的。 我们知道ReentrantLock是可重入锁,也就是说就,上面的操作必须能够成功。
怎么解决这个问题呢? 答案是:在每次做CAS操作之前,先对state的值,进行判断,如果为大于0的话,说明当前锁被占用,然后再去判断owner的的值是不是当前线程,如果是的话,就对state的值做+1的操作,如果不是的话,加锁失败,进入队列,挂起线程,是不是巧妙呢? t1线程重入后的数据如下图
网易云课堂微专业|以ReentrantLock为例理解AQS原理
文章图片

线程排队说完了
-----------------------------------------------------分隔线--------------------------------------------
开始线程出队列
网易云课堂微专业|以ReentrantLock为例理解AQS原理
文章图片

t1线程业务逻辑执行完了,现在要释放持有了。相当于调用了一次reentrantlock的unlock方法, 给state -1,得到的state的是1,根据一开始空锁时,state应该是0所以判断依然是加锁状态。
网易云课堂微专业|以ReentrantLock为例理解AQS原理
文章图片

unlock一次后的状态如上,我们又用t1调用了一次unlock操作,这次我们预先用state减一,拿到预期的结果是0,然后就可以做手脚了,可先把owner置空,然后才把state改为0,这么做的原因是,state为0的时候,owner就要为null,才符合空锁的状态。 现在重点来了,当锁释放成功了以后,我们就要对队列中的元素头部元素进行唤醒也就是调用UnSafe.unpark(obj)方法来对唤醒头部t2, 然后t2被唤醒就会去做获取锁的操作,如果获取成功,就出队列,如果失败,就继续在队列待着。 提一句:我们要对被唤醒的线程做是不是队列头部元素的判断,因为unsafe.park()存在伪唤醒的问题。
网易云课堂微专业|以ReentrantLock为例理解AQS原理
文章图片

t2线程被成功唤醒了,在公平锁或者非公平锁但是没有其他线程与其争抢的情况下,如上所示,只有t2线程去做cas操作,那么他必然就会成功,一旦成功,就会出队列。如下图所示。
t3线程成了队列的头部元素,t2线程结束后执行release,就会释放唤醒t3,循环往复上面的操作。就实现了我们的reentrantlock
【网易云课堂微专业|以ReentrantLock为例理解AQS原理】网易云课堂微专业|以ReentrantLock为例理解AQS原理
文章图片

总结
  1. AQS,就是让线程排队,有序的访问资源,保证资源操作的原子性。
  2. AQS,运用了模板方法模式,当然这篇文章没能体现出来,有兴趣可以看下JDK的源码。

    推荐阅读