本博客系列是学习并发编程过程中的记录总结. 由于文章比较多, 写的时间也比较散, 所以我整理了个目录贴(传送门), 方便查阅.
并发编程系列博客传送门
方法简介
wait 方法
wait 方法是 Object 类中的一个方法. 调用这个方法会让调用线程进入 waiting 状态, 直到另一个线程调用了当前对象上的 notify()或者 notifyAll()方法 (当然, 如果其他线程调用了该线程的 interrupt() 方法, 该线程抛出 InterruptedException 异常返回). 同时如果当前线程已经获取了锁资源, 调用 wait 方法之后会释放这个锁资源.
wait 方法还有一个重载方法 wait(long time), 这个方法会等待 time 时间, 如果在这个时间内没有其他线程来唤醒它的话, 这个线程会自己唤醒继续获得执行机会.
另外需要注意的是, 如果调用 wait()方法的线程没有事先获取该对象的监视器锁, 则调用 wait()方法时调用线程会抛出 IllegalMonitorStateException 异常.
notify 方法
notify 方法会唤醒等待对象监视器的单个线程, 如果等待对象监视器的有多个线程, 则选取其中一个线程进行唤醒到底选择唤醒哪个线程是任意的, 由 CPU 自己决定.
notify 方法还有个兄弟方法 notifyAll, 这个方法会唤醒所有等待监视器对象的线程.
wait-notify 模式的典型应用
wait-notify 模式的一个典型应用就是可以实现生产者 - 消费者模式. 让我印象很深是我毕业那年阿里巴巴年校园招聘的一个笔试题:
有一个苹果箱, 有 10 个人向这个箱子中每次随机放入一个苹果, 有 10 个人每次随机从这个箱子中随机拿走一个苹果, 同时需要满足箱子中的苹果总数不能超过 50 个. 请用代码实现上面的场景(不能使用并发集合框架)
现在看来, 这道题不就是为 wait-notify 模式量身打造的一道题目么. 当时水平有限, 又急急忙忙的, 所以记得当时写的不太好. 这边重新整理下这个代码
- public class AppleBox {
- private int appleCount;
- public synchronized void putApple() {
- while (appleCount>= 50) {
- try {
- this.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- appleCount++;
- String name = Thread.currentThread().getName();
- System.out.println("[" + name + "]放入一个, 当前盒子中苹果数:" + appleCount);
- this.notifyAll();
- }
- public synchronized void takeApple() {
- while (appleCount <= 0) {
- try {
- this.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- appleCount--;
- String name = Thread.currentThread().getName();
- System.out.println("[" + name + "]拿走一个, 当前盒子中苹果数:" + appleCount);
- this.notifyAll();
- }
- private static class AppleTaker implements Runnable {
- private AppleBox appleBox;
- public AppleTaker(AppleBox appleBox) {
- this.appleBox = appleBox;
- }
- @Override
- public void run() {
- while (true) {
- appleBox.takeApple();
- try {
- TimeUnit.MILLISECONDS.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- private static class ApplePutter implements Runnable {
- private AppleBox appleBox;
- public ApplePutter(AppleBox appleBox) {
- this.appleBox = appleBox;
- }
- @Override
- public void run() {
- while (true) {
- appleBox.putApple();
- try {
- TimeUnit.MILLISECONDS.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public static void main(String[] args) {
- AppleBox appleBox = new AppleBox();
- for (int i = 0; i < 20; i++) {
- Thread t = new Thread(new ApplePutter(appleBox));
- t.setName("ApplePutter:" + i);
- t.start();
- }
- for (int i = 0; i < 20; i++) {
- Thread t = new Thread(new AppleTaker(appleBox));
- t.setName("AppleTaker:" + i);
- t.start();
- }
- }
- }
执行结果如下:
[ApplePutter:0]放入一个, 当前盒子中苹果数: 1
[ApplePutter:1]放入一个, 当前盒子中苹果数: 2
[ApplePutter:5]放入一个, 当前盒子中苹果数: 3
[ApplePutter:9]放入一个, 当前盒子中苹果数: 4
[ApplePutter:13]放入一个, 当前盒子中苹果数: 5
[ApplePutter:2]放入一个, 当前盒子中苹果数: 6
[ApplePutter:6]放入一个, 当前盒子中苹果数: 7
[ApplePutter:10]放入一个, 当前盒子中苹果数: 8
[ApplePutter:17]放入一个, 当前盒子中苹果数: 9
[ApplePutter:14]放入一个, 当前盒子中苹果数: 10
[ApplePutter:18]放入一个, 当前盒子中苹果数: 11
[ApplePutter:3]放入一个, 当前盒子中苹果数: 12
[ApplePutter:7]放入一个, 当前盒子中苹果数: 13
[ApplePutter:11]放入一个, 当前盒子中苹果数: 14
[ApplePutter:8]放入一个, 当前盒子中苹果数: 15
[ApplePutter:15]放入一个, 当前盒子中苹果数: 16
[ApplePutter:19]放入一个, 当前盒子中苹果数: 17
[ApplePutter:4]放入一个, 当前盒子中苹果数: 18
[AppleTaker:3]拿走一个, 当前盒子中苹果数: 17
[ApplePutter:12]放入一个, 当前盒子中苹果数: 18
[AppleTaker:1]拿走一个, 当前盒子中苹果数: 17
[AppleTaker:5]拿走一个, 当前盒子中苹果数: 16
[ApplePutter:16]放入一个, 当前盒子中苹果数: 17
[AppleTaker:0]拿走一个, 当前盒子中苹果数: 16
[AppleTaker:12]拿走一个, 当前盒子中苹果数: 15
[AppleTaker:8]拿走一个, 当前盒子中苹果数: 14
[AppleTaker:16]拿走一个, 当前盒子中苹果数: 13
[AppleTaker:7]拿走一个, 当前盒子中苹果数: 12
[AppleTaker:11]拿走一个, 当前盒子中苹果数: 11
[AppleTaker:19]拿走一个, 当前盒子中苹果数: 10
[AppleTaker:9]拿走一个, 当前盒子中苹果数: 9
[AppleTaker:13]拿走一个, 当前盒子中苹果数: 8
[AppleTaker:2]拿走一个, 当前盒子中苹果数: 7
[AppleTaker:6]拿走一个, 当前盒子中苹果数: 6
[AppleTaker:10]拿走一个, 当前盒子中苹果数: 5
[AppleTaker:14]拿走一个, 当前盒子中苹果数: 4
[AppleTaker:4]拿走一个, 当前盒子中苹果数: 3
[AppleTaker:15]拿走一个, 当前盒子中苹果数: 2
[AppleTaker:18]拿走一个, 当前盒子中苹果数: 1
[AppleTaker:17]拿走一个, 当前盒子中苹果数: 0
[ApplePutter:0]放入一个, 当前盒子中苹果数: 1
[ApplePutter:1]放入一个, 当前盒子中苹果数: 2
[ApplePutter:5]放入一个, 当前盒子中苹果数: 3
[ApplePutter:9]放入一个, 当前盒子中苹果数: 4
[ApplePutter:13]放入一个, 当前盒子中苹果数: 5
[ApplePutter:17]放入一个, 当前盒子中苹果数: 6
[ApplePutter:2]放入一个, 当前盒子中苹果数: 7
[ApplePutter:6]放入一个, 当前盒子中苹果数: 8
[ApplePutter:10]放入一个, 当前盒子中苹果数: 9
[ApplePutter:14]放入一个, 当前盒子中苹果数: 10
[ApplePutter:18]放入一个, 当前盒子中苹果数: 11
[ApplePutter:3]放入一个, 当前盒子中苹果数: 12
[ApplePutter:7]放入一个, 当前盒子中苹果数: 13
[ApplePutter:11]放入一个, 当前盒子中苹果数: 14
[ApplePutter:15]放入一个, 当前盒子中苹果数: 15
[ApplePutter:19]放入一个, 当前盒子中苹果数: 16
[AppleTaker:3]拿走一个, 当前盒子中苹果数: 15
[ApplePutter:4]放入一个, 当前盒子中苹果数: 16
[ApplePutter:8]放入一个, 当前盒子中苹果数: 17
[ApplePutter:12]放入一个, 当前盒子中苹果数: 18
wait-notify 模式的经典写法
生产者和消费者的逻辑都可以统一抽象成以下几个步骤:
step1: 获得对象的锁;
step2: 循环判断是否需要进行生产活动, 如果不需要进行生产就调用 wait 方法, 暂停当前线程; 如果需要进行生产活动, 进行对应的生产活动;
step3: 通知等待线程
伪代码如下:
- synchronized(对象) {
- while(条件){
对象. wait();
- }
- // 进行生产或者消费活动
- doSomething();
对象. notifyAll();
}
来源: https://www.cnblogs.com/54chensongxia/p/11995981.html