Java thread communication details

  • 2020-05-12 02:37:39
  • OfStack

Thread communication is used to ensure the coordinated operation of threads, 1 generally in the process of thread synchronization only need to consider the issue of thread communication.

1. Traditional thread communication

Typically, use the three methods provided by the Objeclt class:

wait() causes the current thread to wait and releases the lock on the synchronization monitor until another thread calls the notify () or notifyAll () method of the synchronization monitor to wake up the thread. notify (), wakes up the threads waiting on this synchronization monitor, and optionally selects one wake if there are more than one notifyAll () wakes up all threads that are waiting on this synchronization monitor, and after these threads are scheduled to compete for resources, a thread acquires the lock on this synchronization monitor and is then able to run.

These three methods must be called by the synchronization monitor object and are split into two cases:

When synchronizing methods, these three methods can be called directly because the synchronization monitor is an this object.

Here's an example:


public class SyncMethodThreadCommunication {
  static class DataWrap{
    int data = 0;
    boolean flag = false;
    
    public synchronized void addThreadA(){
      if (flag) {
        try {
          wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      } 
      
      data++;
      System.out.println(Thread.currentThread().getName() + " " + data);
      flag = true;
      notify();
    }
    
    public synchronized void addThreadB() {
      if (!flag) {
        try {
          wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      } 
      
      data++;
      System.out.println(Thread.currentThread().getName() + " " + data);
      flag = false;
      notify();
    }
  }
  
  static class ThreadA extends Thread {
    private DataWrap data;
    
    public ThreadA(DataWrap dataWrap) {
      this.data = dataWrap;
    }
    
    @Override
    public void run() {
      for (int i = 0; i < 10; i++) {
        data.addThreadA();
      }
    }
  }
  
  static class ThreadB extends Thread {
    private DataWrap data;
    
    public ThreadB(DataWrap dataWrap) {
      this.data = dataWrap;
    }
    
    @Override
    public void run() {
      for (int i = 0; i < 10; i++) {
        data.addThreadB();
      }
    }
  }
  
  public static void main(String[] args) {
    // Implementation of two threads in turn to add data 1 operation 
    DataWrap dataWrap = new DataWrap();
    
    new ThreadA(dataWrap).start();
    new ThreadB(dataWrap).start();
  }

}

When synchronizing code blocks, you need to call these three methods using the monitor object.

Here's an example:


public class SyncBlockThreadComminication {
  static class DataWrap{
    boolean flag;
    int data;
  }
  
  static class ThreadA extends Thread{
    DataWrap dataWrap;
    
    public ThreadA(DataWrap dataWrap){
      this.dataWrap = dataWrap;
    }
    
    @Override
    public void run() {
      for(int i = 0 ; i < 10; i++) {
        synchronized (dataWrap) {
          if (dataWrap.flag) {
            try {
              dataWrap.wait();
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
          
          dataWrap.data++;
          System.out.println(getName() + " " + dataWrap.data);
          dataWrap.flag = true;
          dataWrap.notify();
        }  
      }
    }
  }
  
  static class ThreadB extends Thread{
    DataWrap dataWrap;
    
    public ThreadB(DataWrap dataWrap){
      this.dataWrap = dataWrap;
    }
    
    @Override
    public void run() {
      for (int i = 0; i < 10; i++) {
          synchronized (dataWrap) {
            if (!dataWrap.flag) {
              try {
                dataWrap.wait();
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
            }
            
            dataWrap.data++;
            System.out.println(getName() + " " + dataWrap.data);
            dataWrap.flag = false;
            dataWrap.notify();
          }
        }  
      }
      
  }
  public static void main(String[] args) {
    // Implementation of two threads in turn to add data 1 operation 
    
    DataWrap dataWrap = new DataWrap();
    new ThreadA(dataWrap).start();
    new ThreadB(dataWrap).start();
  }

}

2. Use Condition to control thread communication

When Lock objects are used to guarantee synchronization, Condition objects are used to guarantee coordination.

Here's an example:


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.sun.media.sound.RIFFInvalidDataException;

import javafx.scene.chart.PieChart.Data;

public class SyncLockThreadCommunication {
  static class DataWrap {
    int data;
    boolean flag;
    
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    
    public void addThreadA() {
      lock.lock();
      try {
        if (flag) {
          try {
            condition.await();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        
        data++;
        System.out.println(Thread.currentThread().getName() + " " + data);
        flag = true;
        condition.signal();
      } finally {
        lock.unlock();
      }
    }
    
    public void addThreadB() {
      lock.lock();
      try {
        if (!flag) {
          try {
            condition.await();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        
        data++;
        System.out.println(Thread.currentThread().getName() + " " + data);
        flag = false;
        condition.signal();
      } finally {
        lock.unlock();
      }
    }
  }
  
  static class ThreadA extends Thread{
    DataWrap dataWrap;
    
    public ThreadA(DataWrap dataWrap) {
      this.dataWrap = dataWrap;
    }
    
    @Override
    public void run() {
      for (int i = 0; i < 10; i++) {
        dataWrap.addThreadA();
      }
    }
  }
  
  static class ThreadB extends Thread{
    DataWrap dataWrap;
    
    public ThreadB(DataWrap dataWrap) {
      this.dataWrap = dataWrap;
    }
    
    @Override
    public void run() {
      for (int i = 0; i < 10; i++) {
        dataWrap.addThreadB();
      }
    }
  }
  
  public static void main(String[] args) {
    // Implementation of two threads in turn to add data 1 operation 
    
    DataWrap dataWrap = new DataWrap();
    new ThreadA(dataWrap).start();
    new ThreadB(dataWrap).start();
  }

}

Where await(), singal(),singalAll() of Condition objects correspond to wait(),notify(), and notifyAll() methods, respectively.

3. Use blocking queue BlockingQueue to control thread communication

BlockingQueue is a sub-interface of Queue interface, which is mainly used for thread communication. It has one feature: when the producer thread tries to put an element into BlockingQueue, if the queue is full, the thread will be blocked. When a consumer thread attempts to extract an element from BlockingQueue, it is blocked if the queue is empty. These two characteristics correspond to two blocking support methods, put(E e) and take(), respectively

Here's an example:


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueThreadComminication {
  static class DataWrap{
    int data;
  }
  
  static class ThreadA extends Thread{
    private BlockingQueue<DataWrap> blockingQueue;
    
    public ThreadA(BlockingQueue<DataWrap> blockingQueue, String name) {
      super(name);
      this.blockingQueue = blockingQueue;
    }
    
    @Override
    public void run() {
      for (int i = 0; i < 100; i++) {
        try {
          DataWrap dataWrap = blockingQueue.take();
          
          dataWrap.data++;
          System.out.println(getName() + " " + dataWrap.data);
          sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }
  
  static class ThreadB extends Thread{
    private BlockingQueue<DataWrap> blockingQueue;
    private DataWrap dataWrap;
    
    public ThreadB(BlockingQueue<DataWrap> blockingQueue, DataWrap dataWrap, String name) {
      super(name);
      this.blockingQueue = blockingQueue;
      this.dataWrap = dataWrap;
    }
    
    @Override
    public void run() {
      for (int i = 0; i < 100; i++) {
        try {
          dataWrap.data++;
          System.out.println(getName() + " " + dataWrap.data);
          blockingQueue.put(dataWrap);
          sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }
  
  public static void main(String[] args) {
    /// Implementation of two threads in turn to add data 1 operation 
    
    DataWrap dataWrap = new DataWrap();
    BlockingQueue<DataWrap> blockingQueue = new ArrayBlockingQueue<>(1);
    
    new ThreadA(blockingQueue, "Consumer").start();
    new ThreadB(blockingQueue, dataWrap, "Producer").start();
  }

}

BlockingQueue has five implementation classes:

ArrayBlockingQueue array based implementation of BlockingQueue queue

LinkedBlockingQueue BlockingQueue queue based on linked list implementation

The elements in PriorityBlockingQueue need to implement the Comparable interface, where the ordering of the elements is customized according to Comparator.

The SynchronousQueue synchronous queue requires that access to the queue be alternate.

The DelayQueue collection elements must implement the Delay interface, and the elements in the queue are sorted by the return value of the Delay interface method getDelay().


Related articles: