并发编程学习笔记(十三、AQS同步器源码解析2,Lock & Condition & AQS)

目录:

  • Lock & Condition & AQS使用示例
  • Lock & Condition & AQS结构简述
  • Lock与Condition的API解析

Lock & Condition & AQS使用示例

在说Lock和Condition之前我们先来看一下这两个的用法,从用法到源码,一步一步来。

  1 public class ConditionDemo {
  2 
  3     /**
  4      * 创建独占锁
  5      */
  6     private ReentrantLock lock = new ReentrantLock();
  7 
  8     /**
  9      * 创建条件1
 10      */
 11     private Condition condition1 = lock.newCondition();
 12 
 13     /**
 14      * 创建条件2
 15      */
 16     private Condition condition2 = lock.newCondition();
 17 
 18     /**
 19      * 创建条件3
 20      */
 21     private Condition condition3 = lock.newCondition();
 22 
 23     /**
 24      * 日期格式化
 25      */
 26     private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:MM:ss");
 27 
 28     public static void main(String[] args) throws InterruptedException {
 29         ConditionDemo conditionDemo = new ConditionDemo();
 30         Thread1 thread1 = conditionDemo.new Thread1();
 31         Thread2 thread2 = conditionDemo.new Thread2();
 32         Thread3 thread3 = conditionDemo.new Thread3();
 33 
 34         // Alibaba Java Coding Guidelines plugin 插件提示不要直接使用Executors
 35         ExecutorService executorService = Executors.newCachedThreadPool();
 36         executorService.execute(thread1);
 37         executorService.execute(thread2);
 38         executorService.execute(thread3);
 39         Thread.sleep(2000);
 40 
 41         // 依次唤醒各线程任务.
 42         signalTest(conditionDemo);
 43         executorService.shutdown();
 44     }
 45 
 46     /**
 47      * 依次唤醒各线程任务,以观察condition的作用
 48      *
 49      * @param conditionDemo ConditionDemo对象
 50      * @throws InterruptedException 中断异常
 51      */
 52     public static void signalTest(ConditionDemo conditionDemo) throws InterruptedException {
 53         // 创建独占锁 唤醒condition1的线程
 54         conditionDemo.lock.lock();
 55         conditionDemo.condition1.signal();
 56         // 释放独占锁 等待thread1执行完毕.
 57         conditionDemo.lock.unlock();
 58         Thread.sleep(2000);
 59 
 60         // 创建独占锁 唤醒condition2的线程
 61         conditionDemo.lock.lock();
 62         conditionDemo.condition2.signal();
 63         // 释放独占锁 等待thread2执行完毕.
 64         conditionDemo.lock.unlock();
 65         Thread.sleep(2000);
 66 
 67         // 创建独占锁 唤醒condition3的线程
 68         conditionDemo.lock.lock();
 69         conditionDemo.condition3.signal();
 70         // 释放独占锁 等待thread2执行完毕.
 71         conditionDemo.lock.unlock();
 72         Thread.sleep(2000);
 73     }
 74 
 75     /**
 76      * 线程任务定义类
 77      */
 78     public class Thread1 implements Runnable {
 79 
 80         public Thread1() {
 81         }
 82 
 83         @Override
 84         public void run() {
 85             try {
 86                 // 设置线程名称
 87                 Thread.currentThread().setName(Thread1.class.getSimpleName());
 88                 System.out.printf("%s线程启动\n", Thread.currentThread().getName());
 89                 lock.lock();
 90                 // 在condition1上阻塞,并且释放独占锁lock.
 91                 condition1.await();
 92                 System.out.printf("%s线程被唤醒", Thread.currentThread().getName());
 93                 printDate();
 94             }
 95             catch (InterruptedException e) {
 96                 e.printStackTrace();
 97             }
 98             finally {
 99                 lock.unlock();
100             }
101         }
102         
103     }
104 
105     /**
106      * 线程任务定义类
107      */
108     public class Thread2 implements Runnable {
109 
110         public Thread2() {
111         }
112 
113         @Override
114         public void run() {
115             try {
116                 Thread.currentThread().setName(Thread2.class.getSimpleName());
117                 System.out.printf("%s线程启动\n", Thread.currentThread().getName());
118                 lock.lock();
119                 // 在condition2上阻塞,并且释放独占锁lock.
120                 condition2.await();
121                 System.out.printf("%s线程被唤醒", Thread.currentThread().getName());
122                 printDate();
123             }
124             catch (InterruptedException e) {
125                 e.printStackTrace();
126             }
127             finally {
128                 lock.unlock();
129             }
130         }
131         
132     }
133 
134     /**
135      * 线程任务定义类
136      */
137     public class Thread3 implements Runnable {
138 
139         public Thread3() {
140         }
141 
142         @Override
143         public void run() {
144             try {
145                 Thread.currentThread().setName(Thread3.class.getSimpleName());
146                 System.out.printf("%s线程启动\n", Thread.currentThread().getName());
147                 lock.lock();
148                 // 在condition3上阻塞,并且释放独占锁lock.
149                 condition3.await();
150                 System.out.printf("%s线程被唤醒", Thread.currentThread().getName());
151                 printDate();
152             }
153             catch (InterruptedException e) {
154                 e.printStackTrace();
155             }
156             finally {
157                 lock.unlock();
158             }
159         }
160         
161     }
162 
163     /**
164      * 打印时间
165      */
166     void printDate() {
167         System.out.println("【当前时间:" + simpleDateFormat.format(new Date()) + "】");
168     }
169     
170 }

上面代码看似很多单其实逻辑非常简单,只展示了Lock及Condition的基本用法:

  • 首先我们创建一个Lock的可重入锁子类ReentrantLock以及三个Condition执行条件
  • 然后我们为ConditionDemo构建了三个线程,每个线程都会先去从ReentrantLock获取锁然后被对应的Condition阻塞最后完成逻辑后释放锁
  • 最后通过signalTest()函数依次唤醒各线程的任务,其中会先通过lock.lock()获取资源,然后通过condition1.signal()通知对应的线程可执行,最后通过lock.unlock()释放锁

通过上述代码,你可以了解到Condition可阻塞线程的执行(await()),然后满足条件后通知线程可执行(signal())。

这里需要注意的是,Lock一般和Condition配合使用

Lock & Condition & AQS结构简述

通过上述demo,你应该简单了解到了Lock与Condition的基本用法了,现在我们来看看它们的类结构,更进一步知晓它们。

通过ReentrantLock构造函数我们可以了解其根据NonfairSync实现,NonfairSync根据Sync实现,Sync又是根据我们熟悉的AQS实现;并且ReentrantLock是实现了Lock接口的。

而Condition的话同理可得出,其实现类为ConditionObject,其为AQS的内部类,java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject

由此可得出如下类图:

Lock与Condition的API解析

在学习Lock及Condition的API之前,我们先回想下Lock及Condition的作用,分别是对资源的加锁,以及让线程阻塞,等待通知后再执行。

emmmmm,那这不就synchronize以及Object对象的wait、notify嘛(⊙o⊙)…,的确很相似,哈哈。

的确在大多数情况下,内置锁(synchronize)都能很好的工作,但它在功能上存在一些局限性,例如无法实现非阻塞结构的加锁规则等。

而Lock以及Condition的出现正是为了解决这一问题,它实现了可定时可轮询可中断的锁获取操作,公平队列,以及非块结构的锁

注意:

  • 与内置锁不同,Lock是一种显式锁,它更加危险,因为在程序离开被锁保护的代码块时,不会像监视器锁那样自动释放,需要我们手动释放锁
  • 所以在使用Lock时,一定要记得手动释放锁(lock.unlock())

——————————————————————————————————————————————————————————————————————

该说的都说了接下来我们来熟悉下Lock与Condition的API。

1、Lock:

 1 public interface Lock {
 2 
 3     /**
 4      * 阻塞式获取锁。未获取到时线程会被阻塞,不会参与线程调度,直到获取到锁为止;获取锁的过程中不响应中断。
 5      */
 6     void lock();
 7 
 8     /**
 9      * 阻塞式获取锁。在以下两种情况下可中断锁,并抛出InterruptedException,抛出后会清除中断标志位
10      * 1、调用方法时,中断标志位已经设置为true了
11      * 2、在获取锁时线程被中断了,并且获取锁的实现会响应这个中断
12      */
13     void lockInterruptibly() throws InterruptedException;
14 
15     /**
16      * 非阻塞式获取锁。无论成功与否都会理解返回,获取成功返回true,反之false
17      */
18     boolean tryLock();
19 
20     /**
21      * 同tryLock(),但带有超时机制,并且可中断。
22      * 
23      * 若获取到锁立即返回true,反之线程会休眠,直到满足一下三个条件之一:
24      * 1、获取到锁
25      * 2、其它线程中断了当前线程
26      * 3、设定的超时时间到了
27      * 若超时时间到了,还未获取锁,则会返回false。注意,这里超时并不会抛出异常
28      * 
29      * 中断的情况和lockInterruptibly()一致
30      */
31     boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
32 
33     /**
34      * 释放锁,这里需要注意的是,只有拥有锁的线程才能释放锁,而且必须显示的释放锁。
35      */
36     void unlock();
37 
38     /**
39      * 创建一个Condition条件对象
40      */
41     Condition newCondition();
42     
43 }

2、Condition:

上面我们看到了Lock的最后一个方法newCondition(),由此方法便可以的出Condition是与Lock绑定的,而且他们还是一个一对多的关系

那么我们来看下Condition是对象什么:

首先,一个新工具的出现肯定是为了解决某方面的问题的,如果说Lock是为了扩展现有的监视器锁,那么Condition肯定就是对wait、notify的延伸了。

——————————————————————————————————————————————————————————————————————

在说Condition API之前,我觉得我有必要说明下wait、notify的弊端,这样一方面能让你了解到wait、notify为啥在项目中不常见了,另一方面你可以更好的了解Condition的工作原理。

首先,我们调用wait方法的原因通常是线程有一定的条件不满足,我们才会去让其等待。其次,所有调用了wait方法的线程都会在同一个wait set中等待,嗯嗯这看上去很合理。

但这正式这一机制的短板之处,因为每一个wait的线程其实都是在等待同一个notify或notifyAll方法来唤醒自己,这就会导致自己可能会被别的线程的notify方法唤醒。

然而我们知道每个线程的执行条件肯定是不一样的,故会导致我虽然抢到资源取执行逻辑了,但却会发现自己依然不满足执行条件,然后就又wait了。

这无疑使一个浪费CPU资源的操作,所以说最好的方式就是每个线程知道自己在wait什么条件,这样notify的时候也不会唤醒别的线程,这就是Condition的做法。

——————————————————————————————————————————————————————————————————————

Condition的起源介绍完了,最后我们只要说下其API就可以了,其实非常简单,我相信你一下就能看出来其用法:

 1 public interface Condition {
 2 
 3     void await() throws InterruptedException;
 4 
 5     void awaitUninterruptibly();
 6 
 7     long awaitNanos(long nanosTimeout) throws InterruptedException;
 8 
 9     boolean await(long time, TimeUnit unit) throws InterruptedException;
10 
11     boolean awaitUntil(Date deadline) throws InterruptedException;
12 
13     void signal();
14 
15     void signalAll();
16     
17 }

从方法定义上就可以非常明了的知道,我们按照顺序类推:

  • await(),线程等待
  • 等待,但不响应中断
  • 等待相应时间,纳秒单位
  • 等待相应时间,自定义单位
  • 等待到指定时间
  • 唤醒线程
  • 唤醒所有线程

哈哈是不是非常明了。

嗯~~~,说完了Condition之后是不是觉得它和Object的wait、notify非常想,是的非常想,所以我们最后来总结下。

猜你喜欢

转载自www.cnblogs.com/bzfsdr/p/13154793.html
AQS