java并发之AQS详解 ReentrantLock

java并发之AQS详解 ReentrantLock_第1张图片

AQS原理分析
什么是AQS
java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队
列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于
AbstractQueuedSynchronizer(简称AQS) 实现的,AQS是一个抽象同步框架,可以用来实
现一个依赖状态的同步器。
JDK中提供的大多数的同步器如Lock, Latch, Barrier等,都是基于AQS框架来实现的
一般是通过一个内部类Sync继承 AQS
将同步器所有调用都映射到Sync对应的方法
 
AQS具备的特性:
阻塞等待队列
共享/独占
公平/非公平
可重入
允许中断
AQS内部维护属性 volatile int state
state表示资源的可用状态
State三种访问方式:
getState()
setState()
compareAndSetState()
AQS定义两种资源共享方式
Exclusive-独占,只有一个线程能执行,如ReentrantLock
Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
AQS定义两种队列
同步等待队列: 主要用于维护获取锁失败时入队的线程
条件等待队列: 调用await()的时候会释放锁,然后线程会加入到条件队列,调用
signal()唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获得锁
AQS 定义了5个队列中节点状态:
1. 值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
2. CANCELLED,值为1,表示当前的线程被取消;
3. SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
4. CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列
中;
5. PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
不同的自定义同步器竞争共享资源的方式也不同。自定义同步器在实现时只需要实现共享
资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出
队等),AQS已经在顶层实现好了 。自定义同步器实现时主要实现以下几种方法:
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现
它。
tryAcquire(int):独占方式。 尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。 尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。 尝试获取资源。负数表示失败;0表示成功,但
没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。 尝试释放资源,如果释放后允许唤醒后续等待
结点返回true,否则返回false。
同步等待队列
AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明
的一种 基于双向链表数据结构的队列 是FIFO先进先出线程等待队列 ,Java中的CLH队列是原
CLH队列的一个变种,线程由原自旋机制改为阻塞机制。
AQS 依赖CLH同步队列来完成同步状态的管理:
当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造
成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程
当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。 通过signal或signalAll将条件队列中的节点转移到同步队列。( 由条件队列转化为同
步队列

java并发之AQS详解 ReentrantLock_第2张图片

条件等待队列
AQS中条件队列是使用单向列表保存的,用nextWaiter来连接:
调用await方法阻塞线程;
当前线程存在于同步队列的头结点,调用await方法进行阻塞( 从同步队列转化到条
件队列

Condition接口详解
java并发之AQS详解 ReentrantLock_第3张图片

1. 调用Condition#await方法 会释放当前持有的锁,然后阻塞当前线程,同时向
Condition队列尾部添加一个节点 ,所以调用Condition#await方法的时候必须持有锁。
2. 调用Condition#signal方法会 将Condition队列的首节点移动到阻塞队列尾部,然后唤
醒因调用Condition#await方法而阻塞的线程 (唤醒之后这个线程就可以去竞争锁了),所
以调用Condition#signal方法的时候必须持有锁,持有锁的线程唤醒被因调用
Condition#await方法而阻塞的线程。
 
等待唤醒机制之await/signal测试
1 @Slf4j
2 public class ConditionTest {
3
4 public static void main ( String [] args ) {
5
6 Lock lock = new ReentrantLock ();
7 Condition condition = lock . newCondition ();
8
9 new Thread (() ‐> {
10 lock . lock ();
11 try {
12 log . debug ( Thread . currentThread (). getName () + " 开始处理任务 " );
13 condition . await ();
14 log . debug ( Thread . currentThread (). getName () + " 结束处理任务 " );
15 } catch ( InterruptedException e ) {
16 e . printStackTrace ();
17 } finally {
18 lock . unlock ();
19 }
20 }). start ();
21
22 new Thread (() ‐> {
23 lock . lock ();
24 try {
25 log . debug ( Thread . currentThread (). getName () + " 开始处理任务 " );
26
27 Thread . sleep ( 2000 );
28 condition . signal ();
29 log . debug ( Thread . currentThread (). getName () + " 结束处理任务 " );
30 } catch ( Exception e ) {
31 e . printStackTrace ();
32 } finally {
33 lock . unlock ();
34 }
35 }). start ();
36 }
37 }
 
ReentrantLock详解
ReentrantLock是一种基于AQS框架的应用实现 ,是JDK中的一种线程并发访问的同步手
段,它的功能类似于synchronized 是一种互斥锁,可以保证线程安全
相对于 synchronized, ReentrantLock具备如下特点:
可中断
可以设置超时时间
可以设置为公平锁
支持多个条件变量
与 synchronized 一样,都支持可重入
java并发之AQS详解 ReentrantLock_第4张图片
 
顺便总结了几点synchronized和ReentrantLock的区别:
synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;
synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过
ReentrantLock#isLocked判断;
synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;
synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以
被中断的;
在发生异常时synchronized会自动释放锁,而ReentrantLock需要开发者在finally
块中显示释放锁;
ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指
定时长的获取,更加灵活;
synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁(回顾
一下sychronized的唤醒策略),而ReentrantLock对于已经在等待的线程是先来的线程
先获得锁;

ReentrantLock 的使用
同步执行,类似于synchronized
1 ReentrantLock lock = new ReentrantLock (); // 参数默认 false ,不公平锁
2 ReentrantLock lock = new ReentrantLock ( true ); // 公平锁
3
4 // 加锁
5 lock . lock ();
6 try {
7 // 临界区
8 } finally {
9 // 解锁
10 lock . unlock ();
11 }
测试
1 public class ReentrantLockDemo {
2
3 private static int sum = 0 ;
4 private static Lock lock = new ReentrantLock ();
5
6 public static void main ( String [] args ) throws InterruptedException {
7
8 for ( int i = 0 ; i < 3 ; i ++ ) {
9 Thread thread = new Thread (() ‐> {
10 // 加锁
11 lock . lock ();
12 try {
13 for ( int j = 0 ; j < 10000 ; j ++ ) {
14 sum ++ ;
15 }
16 } finally {
17 // 解锁
18 lock . unlock ();
19 }
20 });
21 thread . start ();
22 }
23 Thread . sleep ( 2000 );
24 System . out . println ( sum );
25 }
26 }
可重入
1 @Slf4j
2 public class ReentrantLockDemo2 {
3
4 public static ReentrantLock lock = new ReentrantLock ();
5
6 public static void main ( String [] args ) {
7 method1 ();
8 }
9
10
11 public static void method1 () {
12 lock . lock ();
13 try {
14 log . debug ( "execute method1" );
15 method2 ();
16 } finally {
17 lock . unlock ();
18 }
19 }
20 public static void method2 () {
21 lock . lock ();
22 try {
23 log . debug ( "execute method2" );
24 method3 ();
25 } finally {
26 lock . unlock ();
27 }
28 }
29 public static void method3 () {
30 lock . lock ();
31 try {
32 log . debug ( "execute method3" );
33 } finally {
34 lock . unlock ();
35 }
36 }
37 }
可中断
1 @Slf4j
2 public class ReentrantLockDemo3 { 3
4 public static void main ( String [] args ) {
5 ReentrantLock lock = new ReentrantLock ();
6
7 Thread t1 = new Thread (() ‐> {
8
9 log . debug ( "t1 启动 ..." );
10
11 try {
12 lock . lockInterruptibly ();
13 try {
14 log . debug ( "t1 获得了锁 " );
15 } finally {
16 lock . unlock ();
17 }
18 } catch ( InterruptedException e ) {
19 e . printStackTrace ();
20 log . debug ( "t1 等锁的过程中被中断 " );
21 }
22
23 }, "t1" );
24
25 lock . lock ();
26 try {
27 log . debug ( "main 线程获得了锁 " );
28 t1 . start ();
29 // 先让线程 t1 执行
30 try {
31 Thread . sleep ( 1000 );
32 } catch ( InterruptedException e ) {
33 e . printStackTrace ();
34 }
35 t1 . interrupt ();
36 log . debug ( " 线程 t1 执行中断 " );
37 } finally {
38 lock . unlock ();
39 }
40 }
41 }
锁超时
立即失败 1 @Slf4j
2 public class ReentrantLockDemo4 {
3
4 public static void main ( String [] args ) {
5 ReentrantLock lock = new ReentrantLock ();
6
7 Thread t1 = new Thread (() ‐> {
8
9 log . debug ( "t1 启动 ..." );
10 // 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
11 if ( ! lock . tryLock ()) {
12 log . debug ( "t1 获取锁失败,立即返回 false" );
13 return ;
14 }
15 try {
16 log . debug ( "t1 获得了锁 " );
17 } finally {
18 lock . unlock ();
19 }
20
21 }, "t1" );
22
23
24 lock . lock ();
25 try {
26 log . debug ( "main 线程获得了锁 " );
27 t1 . start ();
28 // 先让线程 t1 执行
29 try {
30 Thread . sleep ( 1000 );
31 } catch ( InterruptedException e ) {
32 e . printStackTrace ();
33 }
34 } finally {
35 lock . unlock ();
36 }
37
38 }
39
40 }
超时失败
1 @Slf4j 2 public class ReentrantLockDemo4 {
3
4 public static void main ( String [] args ) {
5 ReentrantLock lock = new ReentrantLock ();
6 Thread t1 = new Thread (() ‐> {
7 log . debug ( "t1 启动 ..." );
8 // 超时
9 try {
10 if ( ! lock . tryLock ( 1 , TimeUnit . SECONDS )) {
11 log . debug ( " 等待 1s 后获取锁失败,返回 " );
12 return ;
13 }
14 } catch ( InterruptedException e ) {
15 e . printStackTrace ();
16 return ;
17 }
18 try {
19 log . debug ( "t1 获得了锁 " );
20 } finally {
21 lock . unlock ();
22 }
23
24 }, "t1" );
25
26
27 lock . lock ();
28 try {
29 log . debug ( "main 线程获得了锁 " );
30 t1 . start ();
31 // 先让线程 t1 执行
32 try {
33 Thread . sleep ( 2000 );
34 } catch ( InterruptedException e ) {
35 e . printStackTrace ();
36 }
37 } finally {
38 lock . unlock ();
39 }
40
41 }
42
43 }
公平锁
ReentrantLock 默认是不公平的
1 @Slf4j
2 public class ReentrantLockDemo5 {
3
4 public static void main ( String [] args ) throws InterruptedException {
5 ReentrantLock lock = new ReentrantLock ( true ); // 公平锁
6
7 for ( int i = 0 ; i < 500 ; i ++ ) {
8 new Thread (() ‐> {
9 lock . lock ();
10 try {
11 try {
12 Thread . sleep ( 10 );
13 } catch ( InterruptedException e ) {
14 e . printStackTrace ();
15 }
16 log . debug ( Thread . currentThread (). getName () + " running..." );
17 } finally {
18 lock . unlock ();
19 }
20 }, "t" + i ). start ();
21 }
22 // 1s 之后去争抢锁
23 Thread . sleep ( 1000 );
24
25 for ( int i = 0 ; i < 500 ; i ++ ) {
26 new Thread (() ‐> {
27 lock . lock ();
28 try {
29 log . debug ( Thread . currentThread (). getName () + " running..." );
30 } finally {
31 lock . unlock ();
32 }
33 }, " 强行插入 " + i ). start ();
34 }
35 }
36 }
思考: ReentrantLock公平锁和非公平锁的性能谁更高? 条件变量
java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用
Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或
Condition.signalAll() 方法唤醒等待的线程。
注意: 调用Condition的await()和signal()方法,都必须在lock保护之内。
1 @Slf4j
2 public class ReentrantLockDemo6 {
3 private static ReentrantLock lock = new ReentrantLock ();
4 private static Condition cigCon = lock . newCondition ();
5 private static Condition takeCon = lock . newCondition ();
6
7 private static boolean hashcig = false ;
8 private static boolean hastakeout = false ;
9
10 // 送烟
11 public void cigratee (){
12 lock . lock ();
13 try {
14 while ( ! hashcig ){
15 try {
16 log . debug ( " 没有烟,歇一会 " );
17 cigCon . await ();
18
19 } catch ( Exception e ){
20 e . printStackTrace ();
21 }
22 }
23 log . debug ( " 有烟了,干活 " );
24 } finally {
25 lock . unlock ();
26 }
27 }
28
29 // 送外卖
30 public void takeout (){
31 lock . lock ();
32 try {
33 while ( ! hastakeout ){
34 try {
35 log . debug ( " 没有饭,歇一会 " );
36 takeCon . await (); 37
38 } catch ( Exception e ){
39 e . printStackTrace ();
40 }
41 }
42 log . debug ( " 有饭了,干活 " );
43 } finally {
44 lock . unlock ();
45 }
46 }
47
48 public static void main ( String [] args ) {
49 ReentrantLockDemo6 test = new ReentrantLockDemo6 ();
50 new Thread (() ‐> {
51 test . cigratee ();
52 }). start ();
53
54 new Thread (() ‐> {
55 test . takeout ();
56 }). start ();
57
58 new Thread (() ‐> {
59 lock . lock ();
60 try {
61 hashcig = true ;
62 // 唤醒送烟的等待线程
63 cigCon . signal ();
64 } finally {
65 lock . unlock ();
66 }
67
68
69 }, "t1" ). start ();
70
71 new Thread (() ‐> {
72 lock . lock ();
73 try {
74 hastakeout = true ;
75 // 唤醒送饭的等待线程
76 takeCon . signal ();
77 } finally {
78 lock . unlock (); 79 }
80 }, "t2" ). start ();
81 }
82
83 }

你可能感兴趣的:(多线程,java,开发语言,后端)