线程之间的通信

一、为什么要线程通信?

1. 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,

   并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据

2.当然如果我们没有使用线程通信来使用多线程共同操作同一份数据的话,虽然可以实现,

  但是在很大程度会造成多线程之间对同一共享变量的争夺,那样的话势必为造成很多错误和损失!

3.所以,我们才引出了线程之间的通信,多线程之间的通信能够避免对同一共享变量的争夺。

二、什么是线程通信?

  多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作

     就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺

  于是我们引出了等待唤醒机制:(wait()notify()

  就是在一个线程进行了规定操作后,就进入等待状态(wait), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify);

(1)wait()方法:

    线程调用wait()方法,释放它对锁的拥有权,同时他会在等待的位置加一个标志,为了以后使用notify()或者notifyAll()方法  唤醒它时,它好能从当前位置获得锁的拥有权,变成就绪状态,

    要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。  在哪里等待被唤醒时,就在那里开始执行。

(1)notify/notifyAll()方法:

  notif()方法:notify()方法会唤醒一个等待当前对象的锁的线程。唤醒在此对象监视器上等待的单个线程。

 notifAll()方法: notifyAll()方法会唤醒在此对象监视器上等待的所有线程。

 当执行notify/notifyAll方法时,会唤醒一个处于等待该 对象锁 的线程,然后继续往下执行,直到执行完退出对象锁锁住的区域(synchronized修饰的代码块)后再释放锁。

从这里可以看出,notify/notifyAll()执行后,并不立即释放锁,而是要等到执行完临界区中代码后,再释放。故,在实际编程中,我们应该尽量在线程调用notify/notifyAll()后,立即退出临界区。即不要在notify/notifyAll()后面再写一些耗时的代码。

 三、实现线程通信的小demo

  

如大众所说,体现线程通信的鲜明的例子:生产者---消费者是最明显的例子之一。那我们接下来敲一个关于生产者和消费者的简单的小demo(生产者和消费者是一对一的模式),来演示一下线程的通信:

 (1)面包的类(生产和消费方法都在里面哦)

  

/**
*@ClassName Breads
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:19
*@Version 1.0
**/
@Data
public class Breads {
//面包的id
private int bid;
//面包的个数
private int num;

//生产面包的方法(由于是demo,方便大家理解,就把synchronized关键字加到方法上面了哦)
public synchronized void produc(){

//当面包的数量不为0时,该方法处于等待状态
     //多对多的时候 while
if(0 != num){
try {
wait();//等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//当面包数量为0时,那么就开始生产面包了哦
num = num +1;//数量加1
bid = bid + 1 ;//id当然也得加1
String threadname = Thread.currentThread().getName();
System.out.println(threadname+"生产了一个编号为"+bid+"的面包!个数为"+num);
notify();//当执行完后,去唤醒其他处于等待的线程
     //多对多的时候 notifyAll()

}
//消费面包的方法
public synchronized void consume(){
//当面包的数量为0时,该方法处于等待状态
    //多对多的时候 while
if(num == 0 ){
try {
wait();//等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//消费完面包了,所以面包数量降为0了
num = num -1;//数量减1
String name1 = Thread.currentThread().getName();
System.out.println(name1+"买了一个面包编号为"+bid+"个数为"+num);
notify();//当执行完后,去唤醒其他处于等待的线程
    //多对多的时候notifyAll()
}
//有参构造
public Breads(int bid, int num) {
super();
this.bid = bid;
this.num = num;
}

//无参构造
public Breads() {
super();
// TODO Auto-generated constructor stub
}
}

 (2)生产面包的类

/**
*@ClassName producer
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:22
*@Version 1.0
**/
@Data
public class producer implements Runnable {
//获得面包的类
private Breads bre ;
//无参构造
public producer() {
super();
}
//有参构造
public producer(Breads bre) {
super();
this.bre = bre;
}
//继承重写run方法
@Override
public void run() {
pro();
}
//生产面包
private void pro() {
// 本系统默认循环生产20个面包
for (int i = 0; i <20; i++) {
try {
//沉睡0.3秒(演示效果需要,可以不加)
Thread.currentThread().sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用面包类里的生产面包的方法
bre.produc();
}
}
}
(3)消费面包的类
/**
*@ClassName consume
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:24
*@Version 1.0
**/
@Data
public class consume implements Runnable {
//获得面包的类
private Breads bre ;
//继承重写run方法
@Override
public void run() {
con();
}

//消费面包
private void con() {
// 与生产者保持一致,本系统默认循环生产20个面包(生产几个,消费几个)
for(int i = 0;i<20;i++){
try {
//沉睡0.3秒(演示效果需要,可以不加)
Thread.currentThread().sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用面包类里的生产面包的方法
bre.consume();
}
}

//有参构造
public consume(Breads bre) {
super();
this.bre = bre;
}

//无参构造
public consume() {
super();
}
}
(4)测试类
/**
*@ClassName TestBreads
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:25
*@Version 1.0
**/
public class TestBreads {

public static void main(String[] args) {
//new一个面包类
Breads bre = new Breads();
//new一个生产者类
producer proth = new producer(bre);
//new一个消费者类
consume conth = new consume(bre);
//new一个包含消费者类的线程
Thread t1 = new Thread(proth,"生产者");
//new一个包含生产者类的线程
Thread t2 = new Thread(conth,"消费者");
//启动线程
t1.start();
t2.start();
}
}
(5)演示效果

 如上所示,案例表明生产者消费者之间就是运用了wait()和notify()这两个方法,通过通信的沉睡与唤醒机制来完成两个不同线程操作统一数据之间的通信,

当生产者生产出一个面包时,就会陷入沉睡,随后立即去唤醒消费者,就像是对他说,你去买吧,我生产好了,然后消费者就会屁颠屁颠的去买了那个面包,当他吃完那个面包后,

也会陷入沉睡,随后立即唤醒生产者,就像是对他说,你再生产一个吧,我吃完了,然后生产者就... ... 如此循环,周而复始,直到for循环结束为止。

四、多个生产者和消费者

  当我们创建多个生产者和消费者时,上述的代码就会出现一个问题,就是他们无法直到到底要唤醒哪一个,所以这时候我们就用到了notifAll()方法。

  那么代码就不一一再发了,与上面的差不多,唯一不一样的就是在面包类里的if换成while循环判断,notif()换成notifAll()。

  然后再给大家写一下多个生产者和消费者的测试类:

  (1)多个生产者和消费者的测试类

    

/**
*@ClassName TestBreadss
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:26
*@Version 1.0
**/
public class TestBreadss {

public static void main(String[] args) {
//new一个面包类
Breads bre = new Breads();
//new一个生产者类
producer proth = new producer(bre);
//new一个消费者类
consume conth = new consume(bre);
//new三个包含消费者类的线程
Thread t1 = new Thread(proth,"生产者1");
Thread t3 = new Thread(proth,"生产者2");
Thread t5 = new Thread(proth,"生产者3");
//new三个包含生产者类的线程
Thread t2 = new Thread(conth,"消费者1");
Thread t4 = new Thread(conth,"消费者2");
Thread t6 = new Thread(conth,"消费者3");
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
(2)演示多个生产者和消费者通信效果

 如上所示,我们创建了三个生产者和三个消费者,所以我们for循环为三次,也就是会生产和消费各60个面包;

  当进程开始后,会随机开启一个生产者线程生产一个面包后,陷入沉睡,随后会随机唤醒一个消费者线程,

  接着消费掉刚刚生产的那个面包,再次陷入沉睡,随机唤醒一个生产者线程...  周而复始,直到结束。

 本文参考:

  https://www.cnblogs.com/hapjin/p/5492645.html      

  https://www.cnblogs.com/Wenxu/p/7979023.html

如果有什么冲突,请告知我,我可以对文章进行修改

原文地址:https://www.cnblogs.com/xiaowangbangzhu/p/10443103.html