多线程之线程间通信
学习线程通信之前我们需要简单的了解下生产者和消费者模式。
然后我们通过生产者和消费者 学习到线程间通信 等待与唤醒机制。
1 生产者和消费者
- 先来看生产者和消费者
生产者
就是生产东西 如生产商品
消费者
就是消费东西 如 卖商品
- 用代码来描述就是:
System.out.println("生产1");
System.out.println("消费1");
- 用两个线程来表示
package com.company.threadcommunication;
/**
* @description: 线程通信之 生产者 消费者
* @author: tizzy
* @create: 2019-12-18 20:59
**/
public class ThreadCommunication1 {
public static void main(String[] args) {
Communication1 communication = new Communication1();
new Thread(){
@Override
public void run() {
communication.produce();
}
}.start();
new Thread(){
@Override
public void run() {
communication.consumer();
}
}.start();
}
}
class Communication1{
int i = 0;
/**
* 生产者
*/
public void produce(){
while (true){
i++;
System.out.println(" 生产者 ---> " + i);
}
}
/**
* 消费者
*/
public void consumer(){
while (true){
System.out.println(" 消费者 ---> " + i);
}
}
}
启动运行生产者线程和消费者线程后发现 生产者和消费者并不是按照我们的意愿在生产和消费。
可以看到生产者一直在生产,消费者也一直在消费,但是消费者存在重复消费的情况。
我们期望看到生产者生产一个,然后消费者消费一个这样子的运行结果。
但是为什么会出现下面这种情况,原因在于,生产者一直只顾着自己生产,消费者只顾着自己消费,不管有没有被消费过。
如何让两个线程生产一个然后在马上消费一个?我们可以让生产者线程和消费者线程互相通信,当生产者生产一个之后,等着别生产,然后告诉消费者你该消费了,消费者消费完了,先等着别消费了,告诉生产者你赶紧生产 … 一直这样子互相告诉对方信息,放到线程里我们就叫做 线程通信
2 单线程下的线程通信
我们来修改上面的代码
这里需要用到两个方法
线程等待
wait()
让当前线程等着,释放cpu执行权,在当前对象锁中的线程池中排队,将线程临时存储到了线程池中。
当前线程必须拥有此对象的监视器(锁),否则抛出java.lang.IllegalMonitorStateException
线程唤醒
notify()
唤醒等待的一个线程,让其他线程去执行调度
notifyAll()
: 会唤醒线程池中所有的等待的线程。
这些方法必须使用在同步中,因为必须要标识wait、notify等方法所属的锁。同一个锁上的notify,只能唤醒改锁上wait的线程。默认是this.wait();this.notify();this.notifyAll()。
为什么这些方法定义在Object类中,而不是Thread类中呢?
因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然是Object类中的方法。
package com.company.threadcommunication;
/**
* @description: 线程通信
* @author: tizzy
* @create: 2019-12-18 20:59
**/
public class ThreadCommunication2 {
public static void main(String[] args) {
Communication2 communication = new Communication2();
new Thread() {
@Override
public void run() {
while (true){
communication.produce();
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
communication.consumer();
}
}
}.start();
}
}
class Communication2 {
//对象
private final Object object = new Object();
//是否生产
volatile boolean falg = false; //没有生产
int i = 0;
/**
* 生产者
*/
public void produce() {
synchronized (object) {
//true 生产
if (!falg) {
i++;
System.out.println(" 生产 : " + i);
//唤醒 消费者去消费
object.notify();
falg= true;
} else {
//生产了就等着,不在生产,等待消费者去消费
try {
object.wait(); //wait 当前线程处于等待状态 并且释放执行权,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 消费者
*/
public void consumer() {
synchronized (object){
//消费
if(falg){
System.out.println(" 消费 : " + i);
//通知去生产
object.notify();
falg= false;
}else {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果
可以看到,生产者每次生产一个,然后消费者消费一个。
需要注意的是
- 1 wait()、notify()、notifyAll()这些方法必须使用在同步中,因为它们是用来操作同步锁上的线程的状态的;
- 2 同时在使用这些方法时,必须标识它们所属于的锁,标识方式就是:锁对象.wait()、锁对象.notify()、锁对 象.notifyAll()。相同锁的notify(),可以获取相同锁的wait();
- 3 锁可以是任意对象,所以任意对象调用的方法一定定义在Object类中。
3 多线程下生产者消费者
多线程下就不能使用notify()来唤醒线程了,必须使用notifyAll()来唤醒所有等待的线程。
package com.company.threadcommunication;
import java.util.stream.Stream;
/**
* @description: 线程通信
* @author: Administrator
* @create: 2019-12-18 20:59
**/
public class ManyThreadManyCommunication3 {
public static void main(String[] args) {
Communication3 communication = new Communication3();
//多个生产者
Stream.of("p1", "p2", "p3").forEach(s -> {
new Thread() {
@Override
public void run() {
while (true) {
communication.produce(s);
}
}
}.start();
});
//多个消费者
Stream.of("c1", "c2", "c3").forEach(s -> {
new Thread() {
@Override
public void run() {
while (true) {
communication.consumer(s);
}
}
}.start();
});
}
}
class Communication3 {
//对象
private final Object object = new Object();
//是否生产
volatile boolean falg = false; //没有生产
int i = 0;
/**
* 生产者
*/
public void produce(String name) {
synchronized (object) {
//true 生产
while (!falg) {
i++;
System.out.println(name + " 生产 : " + i);
//唤醒 消费者去消费
object.notifyAll();
falg = true;
}
//生产了就等着,不在生产,等待消费者去消费
try {
object.wait(); //wait 等待线程 释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 消费者
*/
public void consumer(String name) {
synchronized (object) {
//消费
while (falg) {
System.out.println( name + " 消费 : " + i);
//通知去生产
object.notifyAll();
falg = false;
}
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
可以看到结果
注意
生产者和消费者 这里使用while去判断是否需要生产和消费标识
//消费
while (falg) {
.....
}
//生产
while (!falg) {
.....
}
需要注意的是:
当多个线程这行到这里时候如果没有使用while
而用if
判断 falg
执行标识,会存在重复生产或消费的情况。
这里假设消费者先抢到cpu执行权,falg 是false 消费者处于等待状态,然后生产者第一个线程进来后发现已经非falg 是true 是需要生产,然后进行生产,此时falg设为false ,第二个生产者线程再进来,if
中条件已经为false 不会去唤醒消费者去消费,直接进行生产数据,这样子重复生产,同理情况下消费者亦是如此。
所以这里需要用到while
去循环判断状态标识,避免重复消费或生产。