wait、notify、notifyAll以及Condition的await、signal,signalAll的用法
线程之间同步可以使用synchronized关键词修饰,也可以使用Lock锁来实现。对应synchronized的有Object类下面的wait、notify、notifyall方法以及ReentrantLock类相关的Condition接口(接口有await、signal、signalAll方法),那么他们怎么使用以及有什么关系和区别呢?我们通过一个例子来讲解一下:
某小工厂是买一款机器产品的,它只有两个部门(一个部门同一时刻只能做一件事),一个部门是生产产品,另一个部门是销售产品,但是该工厂仓库有限,同时只能放一个产品。这种情况下,生产部门和销售部门间的协作会有以下几种:
1、生产部门生产产品时,发现仓库有机器,就需要暂停生产流程,等待销售卖产品。
2、生产部门生产产品时,发现仓库没有机器,开始生产,完成后通知销售部门卖产品。
3、销售部门销售产品时,发现仓库有机器,卖出之后,通知生产部门准备机器。
4、销售部门销售产品时,发现仓库没有机器,先暂停销售流程,等待生产部门生产产品。
通过代码的形式就如下:
package com.test.java;
/**
* TestApplication
* Created by anonyper on 2019/4/10.
*/
//小工厂
public class SmallFactory {
/**
* 仓库
*/
static class Warehouse {
boolean hasProduct = false;//代表仓库有没有产品
/**
* 生产产品
*/
public synchronized void createProduct(int index) throws InterruptedException {
while (hasProduct) {
System.out.println("生产时仓库有产品,等待 "+index);
wait();
}
System.out.println("生产了一个产品 "+index);
hasProduct = true;
notify();//通知可以买产品了
}
/**
* 销售产品
*/
public synchronized void sellProduct(int index) throws InterruptedException {
while (!hasProduct) {
System.out.println("售卖时仓库没有产品,等待 "+index);
wait();
}
System.out.println("卖出了一个产品 "+index);
hasProduct = false;
notify();//通知可以买产品了
}
}
/**
* 生产部门
*/
static class CreateDepartment implements Runnable {
Warehouse warehouse;//和仓库关联
public CreateDepartment(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
try {
this.warehouse.createProduct(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 销售部门
*/
static class SellDepartment implements Runnable {
Warehouse warehouse;//和仓库关联
public SellDepartment(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
this.warehouse.sellProduct(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
new Thread(new CreateDepartment(warehouse)).start();
new Thread(new SellDepartment(warehouse)).start();
}
}
//运行结果
生产了一个产品 0
生产时仓库有产品,等待 1
卖出了一个产品 0
售卖时仓库没有产品,等待 1
生产了一个产品 1
卖出了一个产品 1
售卖时仓库没有产品,等待 2
生产了一个产品 2
生产时仓库有产品,等待 3
卖出了一个产品 2
生产了一个产品 3
生产时仓库有产品,等待 4
卖出了一个产品 3
售卖时仓库没有产品,等待 4
生产了一个产品 4
卖出了一个产品 4
售卖时仓库没有产品,等待 5
生产了一个产品 5
生产时仓库有产品,等待 6
卖出了一个产品 5
售卖时仓库没有产品,等待 6
生产了一个产品 6
卖出了一个产品 6
售卖时仓库没有产品,等待 7
生产了一个产品 7
卖出了一个产品 7
售卖时仓库没有产品,等待 8
生产了一个产品 8
卖出了一个产品 8
售卖时仓库没有产品,等待 9
生产了一个产品 9
卖出了一个产品 9
Process finished with exit code 0
通过ReentrantLock同步锁的实现代码如下:
package com.test.java;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* TestApplication
* Created by anonyper on 2019/4/10.
*/
//小工厂
public class SmallFactory {
/**
* 仓库
*/
static class Warehouse {
boolean hasProduct = false;//代表仓库有没有产品
ReentrantLock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
/**
* 生产产品
*/
public void createProduct(int index) throws InterruptedException {
try{
reentrantLock.lock();
while (hasProduct) {
System.out.println("生产时仓库有产品,等待 "+index);
// wait();
condition.await();
}
System.out.println("生产了一个产品 "+index);
hasProduct = true;
// notify();//通知可以买产品了
condition.signal();
}finally {
reentrantLock.unlock();
}
}
/**
* 销售产品
*/
public void sellProduct(int index) throws InterruptedException {
try {
reentrantLock.lock();
while (!hasProduct) {
System.out.println("售卖时仓库没有产品,等待 "+index);
// wait();
condition.await();
}
System.out.println("卖出了一个产品 "+index);
hasProduct = false;
// notify();//通知可以买产品了
condition.signal();
}finally {
reentrantLock.unlock();
}
}
}
/**
* 生产部门
*/
static class CreateDepartment implements Runnable {
Warehouse warehouse;//和仓库关联
public CreateDepartment(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
try {
this.warehouse.createProduct(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 销售部门
*/
static class SellDepartment implements Runnable {
Warehouse warehouse;//和仓库关联
public SellDepartment(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
this.warehouse.sellProduct(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
new Thread(new CreateDepartment(warehouse)).start();
new Thread(new SellDepartment(warehouse)).start();
}
}
//执行结果:
生产了一个产品 0
生产时仓库有产品,等待 1
卖出了一个产品 0
售卖时仓库没有产品,等待 1
生产了一个产品 1
生产时仓库有产品,等待 2
卖出了一个产品 1
售卖时仓库没有产品,等待 2
生产了一个产品 2
生产时仓库有产品,等待 3
卖出了一个产品 2
售卖时仓库没有产品,等待 3
生产了一个产品 3
生产时仓库有产品,等待 4
卖出了一个产品 3
售卖时仓库没有产品,等待 4
生产了一个产品 4
生产时仓库有产品,等待 5
卖出了一个产品 4
售卖时仓库没有产品,等待 5
生产了一个产品 5
生产时仓库有产品,等待 6
卖出了一个产品 5
生产了一个产品 6
生产时仓库有产品,等待 7
卖出了一个产品 6
生产了一个产品 7
生产时仓库有产品,等待 8
卖出了一个产品 7
售卖时仓库没有产品,等待 8
生产了一个产品 8
生产时仓库有产品,等待 9
卖出了一个产品 8
售卖时仓库没有产品,等待 9
生产了一个产品 9
卖出了一个产品 9
Process finished with exit code 0
总结:
通过例子,我们可以看到synchronized是同步代码块,给线程加锁,统一时刻只能生产或者销售产品,wait是让当前线程处于等待过程,同时释放线程锁,notify唤醒阻塞的线程,继续执行。
- synchronized下的wait对应Condtion的await,让当前线程阻塞并释放线程锁的作用。
- synchronized下的notify对应Condtion的signal,随机唤醒阻塞线程继续执行。
- synchronized下的notifyAll对应Condtion的signalAll,唤醒所有阻塞线程继续执行。
上述只有两个部门,就意味着只有两个线程在运行,所以一个运行唤醒阻塞的必然是另一个,这种情况我们看到synchronized和ReentrantLock除了书写不一样,没什么使用上的区别。
现在这个小工厂因为效益好,扩大规模了,成立了5个生产部门和5个销售部门,但是仓库还是只有一个(多个其实是一样的),main方法中的代码改动如下:
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
for (int i = 0; i <5 ; i++) {
new Thread(new CreateDepartment(warehouse)).start();
new Thread(new SellDepartment(warehouse)).start();
}
}
这个时候不管是synchronized也好,还是上面的Condtion,都可能出现下面的运行结果:
生产了一个产品 0
生产时仓库有产品,等待 0
卖出了一个产品 0
售卖时仓库没有产品,等待 1
生产了一个产品 1
生产时仓库有产品,等待 2
生产时仓库有产品,等待 0
卖出了一个产品 0
售卖时仓库没有产品,等待 0
生产了一个产品 0
生产时仓库有产品,等待 1
生产时仓库有产品,等待 0
生产时仓库有产品,等待 0
卖出了一个产品 1
售卖时仓库没有产品,等待 2
生产了一个产品 2
生产时仓库有产品,等待 3
卖出了一个产品 1
售卖时仓库没有产品,等待 2
生产了一个产品 0
生产时仓库有产品,等待 1
卖出了一个产品 0
售卖时仓库没有产品,等待 1
售卖时仓库没有产品,等待 0
售卖时仓库没有产品,等待 0
生产了一个产品 1
生产时仓库有产品,等待 0
生产时仓库有产品,等待 0
卖出了一个产品 2
生产了一个产品 3
生产时仓库有产品,等待 4
生产时仓库有产品,等待 2
卖出了一个产品 2
售卖时仓库没有产品,等待 3
售卖时仓库没有产品,等待 3
生产了一个产品 1
生产时仓库有产品,等待 2
卖出了一个产品 1
售卖时仓库没有产品,等待 2
售卖时仓库没有产品,等待 0
售卖时仓库没有产品,等待 0
//线程阻塞了
为什么会这样呢?因为notify和signal是随机唤醒一个阻塞的线程,当多个线程都在运行时,如上运行结果就是售卖线程卖出了之后,随机唤醒的线程还是销售的线程,这个时候进入阻塞之后就没有人可以唤醒了,这就是死锁情况。
怎么解决呢?简单粗暴的方法就是随机唤醒一个线程可能出错,那么我将所有阻塞的线程都唤醒不就可以了么,将上面的notify替换成notifyAll或者signal替换成signalAll,线程也就不会死锁了。(这也是判断用while不用if的原因)。
上面的解决方案虽然可行,但是不太合理,那么有没有针对性的唤醒呢?比如销售线程唤醒的就是生产线程,生产线程唤醒的就是销售线程,那么这个问题就解决了吧。这里就要用到Condtion来精确控制了。
同一个ReentrantLock对象可以实例化不同的Condtion对象,通过对Condtion唤醒的控制,可以实现不通过线程的精确唤醒:
package com.test.java;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* TestApplication
* Created by anonyper on 2019/4/10.
*/
//小工厂
public class SmallFactory {
/**
* 仓库
*/
static class Warehouse {
boolean hasProduct = false;//代表仓库有没有产品
ReentrantLock reentrantLock = new ReentrantLock();
Condition createCondition = reentrantLock.newCondition();//控制生产
Condition sellCondition = reentrantLock.newCondition();//控制销售
/**
* 生产产品
*/
public void createProduct(int index) throws InterruptedException {
try{
reentrantLock.lock();
while (hasProduct) {
System.out.println("生产时仓库有产品,等待 "+index);
// wait();
createCondition.await();
}
System.out.println("生产了一个产品 "+index);
hasProduct = true;
// notify();//通知可以买产品了
sellCondition.signal();
}finally {
reentrantLock.unlock();
}
}
/**
* 销售产品
*/
public void sellProduct(int index) throws InterruptedException {
try {
reentrantLock.lock();
while (!hasProduct) {
System.out.println("售卖时仓库没有产品,等待 "+index);
// wait();
sellCondition.await();
}
System.out.println("卖出了一个产品 "+index);
hasProduct = false;
// notify();//通知可以买产品了
createCondition.signal();
}finally {
reentrantLock.unlock();
}
}
}
/**
* 生产部门
*/
static class CreateDepartment implements Runnable {
Warehouse warehouse;//和仓库关联
public CreateDepartment(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
try {
this.warehouse.createProduct(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 销售部门
*/
static class SellDepartment implements Runnable {
Warehouse warehouse;//和仓库关联
public SellDepartment(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
this.warehouse.sellProduct(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
for (int i = 0; i <5 ; i++) {
new Thread(new CreateDepartment(warehouse)).start();
new Thread(new SellDepartment(warehouse)).start();
}
}
}
以上就是线程通信涉及到的wait、notify、notifyAll以及Condition的await、signal,signalAll的用法。
ps:
wait、notify、notifyAll 只能用在synchronized内部,且被监控的对象调用。