前言
wait和notify这两个方法通常在线程间通信中常常用到,其中wait方法和sleep方法虽然都能够让线程“停下来”,但是有很大的区别,另外,耳熟能详的生产者消费者问题也涉及到wait和notify,本篇就简单的整理一下这些问题吧!
一、wait()/notify()的简单使用
用一个例子来看一下简单的使用:假设有两个线程,一个线程用来判断数组中长度是否达到5,达到的时候通知一下,另一个线程用来往数组中添加元素。
1 | public class demo1 { |
执行结果
start…Thread-0
start…Thread-1
当前list的数量 1
当前list的数量 2
当前list的数量 3
当前list的数量 4
当前list的数量 5
释放锁,但是当前线程还需要继续执行下去…
当前list的数量 6
当前list的数量 7
当前list的数量 8
当前list的数量 9
当前list的数量 10
end…Thread-1
end…Thread-0
从结果可以看出,Thread1是先抢占到锁的,但是由于执行了wait(),立即释放对锁的控制,Thread2抢占到锁,Thread2立即执行,当长度等于5的时候,执行了notify()命令,唤醒Thread1,但不是立即释放掉锁,而是要等Thread2执行完毕后才释放掉锁,所以数组元素一直添加到10后,Thread2释放掉锁,然后Thread1继续执行wait()后面的操作。
总结起来是这样的,wait()命令会直接释放掉锁,但是notify()要等到执行完notify()方法所在的同步synchronized代码块后才释放锁,注意,这边是执行完synchronized代码块!!!
既然wait()是暂停了线程,那么和sleep()有什么区别?其实wait()是立即释放掉锁,还让出CPU的执行权,进入等待,但是sleep()是不释放锁,相当于将当前线程进入阻塞状态(BLOCKED),仅仅会让出CPU的执行权。这里最经典的一个问题就是生产者消费者问题。
二、生产者消费者问题
简单来说,生产者消费者问题是指在一个系统中,有生产者和消费者两种角色,生产者生产消费者需要的资料,消费者消费生产出来的资料,当生产者生产的资料不够的时候,消费者等待,当生产者生产出资料后,通知消费者来拿资料。
1 | package test.waitAndnotify; |
代码的执行结果
Thread-0 生产出0
Thread-0 生产出1
Thread-0 生产出2
Thread-0 生产出3
消费者消费 0
Thread-0 生产出4
Thread-0 生产出5
Thread-0 生产者生产数量已满,等待消费者线程消费
消费者消费 1
Thread-0 生产出6
Thread-0 生产者生产数量已满,等待消费者线程消费
消费者消费 2
消费者消费 3
消费者消费 4
消费者消费 5
消费者消费 6
Thread-0 生产出7
Thread-0 生产出8
Thread-0 生产出9…………
同样的,也能设置多个生产者消费者
1 | public static void main(String[] args) { |
代码的执行结果
Thread-0 生产出0
Thread-0 生产出1
Thread-0 生产出2
Thread-0 生产出3
Thread-4 消费者消费 0
Thread-4 消费者消费 1
Thread-4 消费者消费 2
Thread-4 消费者消费 3
Thread-4 当前生产队列为空,消费者等待生产者生产
Thread-3 当前生产队列为空,消费者等待生产者生产
Thread-2 当前生产队列为空,消费者等待生产者生产
Thread-1 生产出0
Thread-1 生产出1
Thread-1 生产出2
Thread-1 生产出3
Thread-1 生产出4
Thread-1 生产者生产数量已满,等待消费者线程消费
Thread-2 消费者消费 0
Thread-2 消费者消费 1
Thread-3 消费者消费 2
Thread-3 消费者消费 3
Thread-3 消费者消费 4
Thread-3 当前生产队列为空,消费者等待生产者生产
Thread-4 当前生产队列为空,消费者等待生产者生产
Thread-0 生产出4
Thread-0 生产出5……
但是要注意了,这边同步代码块synchronized中的条件要是while()而不能是if(),这是为什么?我们来将while()改成if()试试,结果如下:
1 | Thread-0 生产出0 |
很明显,发生了错误。来分析一下,比如说当前执行到一个生产者生产出了一个数字,此时队列中恰好只有两个数字,接着唤醒了其他的线程,所以三个消费者线程都被唤醒,要进行消费,但是当消费者线程1和消费者线程2都消费完后,消费者线程3要进行消费了,由于他们都是从wait()后面开始执行的,消费者线程1和2是没有问题的,因为队列中有两个数字可供消费,但是当消费者线程3开始执行后,由于是从wait()后面开始执行的,就直接跳出if()判断,进行消费,而此时队列中没有其他数字来供他消费,所以报错。也就是上面报错的原因。
但是,当改成while()循环的时候,线程3会回到while()这里进行再判断,当队列中没有元素,就继续while()循环,而不是执行下去,这样就不会发生错误。