目录:
- 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线程启动 ", 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线程启动 ", 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线程启动 ", 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非常想,是的非常想,所以我们最后来总结下。