JAVA 多线程之间的通信

发布于 2021-06-29 12:45:33 字数 8419 浏览 1204 评论 0

一、线程间通信 - 示例

多个线程在处理同一个资源,但是任务不同。

//资源
class Resource{
  String name;
  String sex;
}

//输入
class Input implement Runnable{
  Resource r;
  Input(Resource r){
    this.r = r;
  }
  public void run(){
    int x = 0;
    while(true){
      if(x%2==0){
        r.name = "mike";
        r.sex = "man";
      }else{
        r.name = "丽丽";
        r.sex = "女";
      } 
      x++;  
    }
  }
}

//输出
class Output implement Runnable{
  Resource r;
  Output(Resource r){
    this.r = r;
  }
  public void run(){
    while(true){
      System.out.println(r.name+"..."+r.sex);
    }
  }
}

class Demo{
  public static void main(String[] args){
    Resource r = new Resource();
    Input i = new Input(r);
    Output o = new Output(r);

    Thread t1 = new Thread(i);
    Thread t2 = new Thread(o);

    t1.start();
    t2.start();
  }
}

由于读和写不同步,所以会造成数据混乱,修改如下:

//输入
class Input implement Runnable{
  Resource r;
  Input(Resource r){
    this.r = r;
  }
  public void run(){
    int x = 0;
    while(true){
      synchronized(this.r){
        if(x%2==0){
          r.name = "mike";
          r.sex = "man";
        }else{
          r.name = "丽丽";
          r.sex = "女";
        } 
        x++;
      }  
    }
  }
}

//输出
class Output implement Runnable{
  Resource r;
  Output(Resource r){
    this.r = r;
  }
  public void run(){
    while(true){
      synchronized(this.r){
        System.out.println(r.name+"..."+r.sex);
      }
    }
  }
}

二、线程间通信 - 等待唤醒机制 - wait() - notify()

涉及的方法

  1. wait():是让线程处于冻结状态,释放 cpu 的执行权和执行资格。被 wait 的线程会被存储到线程池中。
  2. notify():唤醒线程池中一个线程(任意)。
  3. notifyAll():唤醒线程池中的所有线程。

注意:这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪一个锁上的线程。

为什么操作线程的方法定义在Object类中? 因为这些方法是监视器的方法,监视器其实就是锁,而锁就是任意的对象,所以任意的对象可以调用的方法必须定义在Object中。

//资源
class Resource{
  String name;
  String sex;
  boolean flag = false;
}

//输入
class Input implement Runnable{
  Resource r;
  Input(Resource r){
    this.r = r;
  }
  public void run(){
    int x = 0;
    while(true){
      synchronized(this.r){
        if(this.r.flag){
          try{
            this.r.wait();
          }catch(InterruptedException){}
        }

        if(x%2==0){
          this.r.name = "mike";
          this.r.sex = "man";
        }else{
          this.r.name = "丽丽";
          this.r.sex = "女";
        }
        this.r.flag = true;
        this.r.notify();
        x++;
      }  
    }
  }
}

//输出
class Output implement Runnable{
  Resource r;
  Output(Resource r){
    this.r = r;
  }
  public void run(){
    while(true){
      synchronized(this.r){
        if(!this.r.flag){
          try{
            this.r.wait();
          }catch(InterruptedException){}
        }
        System.out.println(this.r.name+"..."+this.r.sex);
        this.r.flag = false;
        this.r.notify();
      }
    }
  }
}
class Demo{
  public static void main(String[] args){
    Resource r = new Resource();
    Input i = new Input(r);
    Output o = new Output(r);

    Thread t1 = new Thread(i);
    Thread t2 = new Thread(o);

    t1.start();
    t2.start();
  }
}

三、线程间通信-多生产者多消费者问题-wait()-notifyAll()

if 判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况 while 判断标记,解决了线程获取执行权后是否要运行(回过头判断)

notify 只能唤醒一个线程,如果奔放唤醒了本方就没有意义。而且 while 判断标记 + notify 会导致死锁 notifyAll 解决了本方线程一定会唤醒对方线程

class Resource{
  private String name;
  private int count = 1;
  private boolean flag = false;

  public synchronized void setName(String name){
    if(this.flag){//多生产者这里不能在用if,必须用while,进程唤醒之后再进行判断标记
      try{this.wait();}catch(InterruptedException e){}
    }
    this.name = name + this.count;
    this.count++;
    System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);

    this.flag = true;
    this.notify();//多生产者这里必须使用notifyAll()唤醒所有线程,不然会导致所有线程都被休眠现象(死锁)
  }

  public synchronized void out(){
    if(!this.flag){//多生产者这里不能在用if,必须用while,进程唤醒之后再进行判断标记
      try{this.wait();}catch(InterruptedException e){}
    }
    System.out.println(Thread.currentThread().getName()+"......消费者......"+this.name);
    this.flag = false;
    this.notify();
  }
}

class Producer implement runnable{
  private Resource r;
  Producer(Resource r){
    this.r = r;
  }

  public void run(){
    while(true){
       this.r.setName("烤鸭"); 
    }
  }
}

class Consummer implement runnable{
  private Resource r;
  Consummer(Resource r){
    this.r = r;
  }

  public void run(){
    while(true){
       this.r.out(); 
    }
  }
}

class ProducerConsumerDemo{
  public static void main(String[] args){
    Resource r = new Resource();

    Producer p = new Producer(r);
    Consummer c = new Consummer(r);

    //多生产者
    Thread t0 = new Thread(p);
    Thread t1 = new Thread(p);

    //多消费者 
    Thread t2 = new Thread(c);
    Thread t3 = new Thread(c);

    t1.start();
    t2.start();
  }
}

多生产者多消费者使用 notify() 是造成另一种死锁的原因。

四、线程间通信 - 多生产者多消费者问题 - JDK1.5 新特性- Lock

对于同步代码块,对于锁的操作是隐式的。

import java.util.concurrent.locks.*;
class Resource{
  private String name;
  private int count = 1;
  private boolean flag = false;

  Look look = new ReentrantLock();//jdk1.5新特性-创建一个锁对象

  public void setName(String name){
    lock.lock();//使用显式锁

    try{
      while(this.flag){//此处就不能再用this了,要用锁上的wait
        try{this.wait();}catch(InterruptedException e){}
      }
      this.name = name + this.count;
      this.count++;
      System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);

      this.flag = true;
      this.notify();//此处就不能再用this了
    }finally{
      lock.unlock();
    }
  }

  public void out(){
    try{
      if(!this.flag){//此处就不能再用this了
        try{this.wait();}catch(InterruptedException e){}
      }
      System.out.println(Thread.currentThread().getName()+"......消费者......"+this.name);
      this.flag = false;
      this.notify();//此处就不能再用this了
    }finally{
      lock.unlock();
    }   
  }
}

Lock 接口:出现替代了同步代码块或者同步函数,将同步的隐式锁操作变为显式锁操作。同时更为灵活。可以在一个锁上加上多个监视器

五、线程间通信 - 多生产者多消费者问题 - JDK1.5 新特性-Condition

import java.util.concurrent.locks.*;
class Resource{
  private String name;
  private int count = 1;
  private boolean flag = false;

  Look look = new ReentrantLock();//jdk1.5新特性-创建一个锁对象

  //通过已有的锁获取该锁上的监视器对象
  Condition con1 = lock.newCondition();//两组监视器,一组监视生产者,一组监视消费者
  Condition con2 = lock.newCondition();

  public void setName(String name){
    lock.lock();//使用显式锁

    try{
      while(this.flag){
        try{con1.await();}catch(InterruptedException e){}//此处就不能再用this了,要用锁上的wait
      }
      this.name = name + this.count;
      this.count++;
      System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);

      this.flag = true;
      //this.notify();//此处就不能再用this了
      con2.signalAll();/con2.signal();
    }finally{
      lock.unlock();
    }
  }

  public void out(){
    try{
      if(!this.flag){//此处就不能再用this了
        try{con2.await();}catch(InterruptedException e){}
      }
      System.out.println(Thread.currentThread().getName()+"......消费者......"+this.name);
      this.flag = false;
      //this.notify();//此处就不能再用this了
      con1.signalAll();/con1.signal();
    }finally{
      lock.unlock();
    }   
  }
}

Condition 接口:出现替代了 Object 中的 wait notify notifyAll 方法。将这些监视器方法单独进行了封装,变成 Condition 监视器对象,可以任意锁进行组合 await signal signalAll。

六、wait 和 sleep 的区别

1、wait 可以指定时间也可以不指定。sleep必须指定时间。

2、在同步中,对 cpu 的执行权和锁的处理不同

  • wait:释放执行权,释放锁
  • sleep:释放执行权,不释放锁。

七、停止线程

1、stop()

已过时

2、run方法结束

任务中都会有循环结构,只要控制住循环就可以结束任务。 控制循环通常就用定义标记来完成。

但是如果线程处于冻结状态,无法读取标记,如何结束呢?

可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。 但是强制动作会发生 InterruptedException,记得要处理

八、守护线程 - setDaemon

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出,该方法必须在启动线程前调用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84960 人气
更多

推荐作者

1CH1MKgiKxn9p

文章 0 评论 0

ゞ记忆︶ㄣ

文章 0 评论 0

JackDx

文章 0 评论 0

信远

文章 0 评论 0

yaoduoduo1995

文章 0 评论 0

霞映澄塘

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文