005 - Java线程之间通信和同步

  • 并发编程中需要考虑, 线程之间如何通信,如何解决同步问题

1 线程之间通信

1.1 线程之间的通信

线程的通信是指线程之间以何种机制来交换信息,

目的是为了能够让线程之间相互发送信号。另外,线程通信还能够使得线程等待其它线程的信号,

更多细节可以参考线程之间的通信(thread signal)

  • 在命令式编程中,线程之间的通信机制有两种共享内存消息传递

  • 通过共享内存实现线程通信,比如下边代码中,用共享对象实现线程A和线程B的通信

    • 下边代码主体结构 : 线程B等待线程A计算完成,输出其计算结果

      // 共享对象,用于两个线程A对通信 : 
      public class MySignal {
          private boolean hasDataToProcess;
      
          public synchronized void setHasDataToProcess(boolean hasData){
              this.hasDataToProcess=hasData;
          }
          public synchronized boolean hasDataToProcess(){
              return this.hasDataToProcess;
          }
      
      }
      //  ThreadA计算
      public class ThreadA  extends Thread{
      
          int count;
      
          MySignal mySignal;
      
          public ThreadA(MySignal mySignal) {
              this.mySignal = mySignal;
          }
      
          @Override
          public void run() {
              for (int i =0; i < 10 ;i++){
                  count++;
              }
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              mySignal.setHasDataToProcess(true);
          }
      }
      
      // ThreadB 循环等待,一直到共享内存中的值发生变化
      public class ThreadB extends Thread {
      
          MySignal mySignal;
      
          ThreadA threadA;
      
      
          public ThreadB(MySignal mySignal, ThreadA threadA) {
              this.mySignal = mySignal;
              this.threadA = threadA;
          }
      
          @Override
          public void run() {
              while (true){
                  if (mySignal.hasDataToProcess()){
                      System.out.println("线程A的计算结果--"+threadA.count);
                      break;
                  }
      
              }
          }
       // 测试代码 : 
       	public static void main(String[] args) {
              MySignal mySignal = new MySignal();
              ThreadA threadA = new ThreadA(mySignal);
              ThreadB threadB = new ThreadB(mySignal,threadA);
              threadA.start();
              threadB.start();
          }
      }
      
    • 输出结果

    线程A的计算结果--10
    
  • 上边代码只是线程通过共享内存实现线程通信的一个例子,实际上,有很大cpu浪费 ,属于 线程的忙等

    • 线程A一直在等待数据就绪,或者说线程A一直在等待线程B设置hasDataToProcess的信号值为true

      @Override
          public void run() {
              while (true){
                  if (mySignal.hasDataToProcess()){
                      System.out.println("线程A的计算结果--"+threadA.count);
                      break;
                  }
      
              }
          }
      
    • 上面代码一直在执行循环,直到hasDataToProcess被设置为true

    • 忙等意味着线程还处于运行状态,一直在消耗CPU资源,所以,忙等不是一种很好的现象

    • 解决方式 : java.lang.Object提供的wait()、notify()、notifyAll()方法就可以解决忙等问题

  • wait()、notify()、notifyAll()

    • 上边三个方法是 Java提供的一种内联机制,解决线程忙等问题

    • 思路 : 让线程在等待信号时进入非运行状态,

      • 当一个线程调用任何对象上的wait()方法时便会进入非运行状态

        • 比如 下边代码就是调用的监控对象的wait()

          synchronized (monitorObject){
             monitorObject.notify();
          }
          
      • 直到另一个线程调用同一个对象上的notify()或notifyAll()方法

    • 利用wait() notify() 处理上边问题

      // 监控对象
      public class MonitorObject {
      
      }
      // ThreaD负责计算,在计算完成之后,唤醒被阻塞的ThreadC
      public class ThreadD extends Thread{
          int count;
          MonitorObject mySignal;
          public ThreadD(MonitorObject mySignal){
              this.mySignal=mySignal;
          }
          @Override
          public void run(){
              for(int i=0;i<100;i++){
                  count=count+1;
              }
              try {
                  Thread.sleep(500);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              synchronized (mySignal){
                  mySignal.notify();//计算完成调用对象的notify()方法,唤醒因调用这个对象wait()方法而挂起的线程
              }
          }
      }
      // ThreadC在阻塞中
      public class ThreadC extends Thread{
          MonitorObject mySignal;
          ThreadD threadD;
          public ThreadC(MonitorObject mySignal, ThreadD threadD){
              this.mySignal=mySignal;
              this.threadD=threadD;
          }
          @Override
          public void run(){
             synchronized (mySignal){
                 try {
                     mySignal.wait();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println("线程B计算结果为:"+threadD.count);
             }
          }
          public static void main(String[] args) {
              MonitorObject mySignal=new MonitorObject();
              ThreadD threadD=new ThreadD(mySignal);
              ThreadC threadC=new ThreadC(mySignal,threadD);
              threadC.start();
              threadD.start();
          }
      }
      
    • 上边代码中, 线程C因调用了监控对象的wait()方法而挂起,线程D通过调用监控对象的notify()方法唤醒挂起的线程C

    • 注意 : 上边调用wait和notify都是在 synchronized 同步代码块中, 原因

      • 一个线程在没有获的对象锁的前提下调用了这个对象的wait或者notify方法,会抛出异常 IllegalMonitorStateException
    • 注意 :调用对象wait()方法的线程需要获得这个对象的锁,那么这会不会阻塞其它线程调用这个对象的notify()方法呢? 答案是不会阻塞

      • 因为 : 当一 法。
    • 注意 : 一个线程调用一个对象的notify()方法,则会唤醒正在等待这个对象所有线程中的一个线程(唤醒的线程是随机的

1.2 线程之间的同步
  • 同步是指程序用于控制不同线程之间操作发生相对顺序的机制
    • 在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行
    • 在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的
上一篇:Java集合005 --- ConcurrentHashMap


下一篇:每周一道算法题005:切木棒