一、前言
线程通信是Java并发编程中的基础。曾经面试的时候遇到一个比较有趣的面试题拿出来与大家分享。
题目:
2个线程A和B,A线程生产A数据,然后消费B数据;B线程生产B数据,然后消费A数据,请用代码实现。
题目分析:
这是个典型的线程间通信的题目,由于AB分别需要消费对方生产的数据,需要AB在生产完成后通知对方线程消费,同时也需要在没有数据的时候阻塞等待。有没有觉得很熟悉?类似交替打印的问题?
 二、题解
下边,我将用2种思路进行解答。
 思路一: wait/notify
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
   | 
 
 
 
 
 
 
  public class ThreadLockDemo2 {
      private String A = null;     private String B = null;
      private Object lock = new Object();
      public void doA() {                  A = "A";
                   synchronized (lock) {             if (B == null) {                 try {                                                               lock.wait();                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }                          System.out.println(Thread.currentThread().getName() + ":" + B);             lock.notify();         }     }
      public void doB() {                  B = "B";                  synchronized (lock) {             if (A == null) {                 try {                     lock.wait();                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }                          System.out.println(Thread.currentThread().getName() + ":" + A);             lock.notify();         }     }
 
      public static void main(String[] args) {
          ThreadLockDemo2 threadLockDemo = new ThreadLockDemo2();         new Thread(new Runnable() {             @Override             public void run() {                 threadLockDemo.doA();             }         }, "thread-A").start();
          new Thread(new Runnable() {             @Override             public void run() {                 threadLockDemo.doB();             }         }, "thread-B").start();     } }
 
  | 
 
 思路二: Condition/await/signal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
   | 
 
 
 
 
 
 
  public class ThreadLockDemo {
      private String A = null;     private String B = null;
      private ReentrantLock lock = new ReentrantLock();     private Condition conditionA = lock.newCondition();     private Condition conditionB = lock.newCondition();
      public void doA() {                  lock.lock();         try {                          A = "A";             if (B == null) {                                  conditionA.await();             }                          System.out.println(Thread.currentThread().getName() + ":" + B);
                           conditionB.signal();         } catch (Exception e) {             e.printStackTrace();         } finally {             lock.unlock();         }     }
      public void doB() {                  lock.lock();         try {                          B = "B";             if (A == null) {                 conditionB.await();             }                          System.out.println(Thread.currentThread().getName() + ":" + A);
                           conditionA.signal();         } catch (Exception e) {             e.printStackTrace();         } finally {             lock.unlock();         }     }
 
      public static void main(String[] args) {         ThreadLockDemo threadLockDemo = new ThreadLockDemo();         new Thread(new Runnable() {             @Override             public void run() {                 threadLockDemo.doA();             }         }, "thread-A").start();
          new Thread(new Runnable() {             @Override             public void run() {                 threadLockDemo.doB();             }         }, "thread-B").start();     } }
 
 
  | 
 
 三、注意
- 
当前线程必须拥有此对象的monitor(即锁),才能调用某个对象的wait()方法能让当前线程阻塞。(这种阻塞是通过提前释放synchronized锁,进入等待队列导致的阻塞,这种请求必须有其他线程通过notify()或者notifyAll()唤醒重新竞争获得锁)
 
- 
调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程; (notify()或者notifyAll()方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁)
 
- 
调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程,唤醒的线程获得锁的概率是随机的,取决于cpu调度
 
下边我们来验证下2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
   | 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  public class WaitDemo {
      private int index = 0;     Object lock = new Object();
      public void method1() {         try {             synchronized (lock) {                 if (index != 3) {                     System.out.println(Thread.currentThread().getName() + " wait before...");                     lock.wait();                     System.out.println(Thread.currentThread().getName() + " wait after...");                 }             }         } catch (InterruptedException e) {             e.printStackTrace();         }     }
      public void method2() {         try {             synchronized (lock) {                 for (int i = 0; i < 6; i++) {                     index ++;                     if (i == 3) {                         lock.notify();                         System.out.println(Thread.currentThread().getName() + " notify...");                     }                     System.out.println(Thread.currentThread().getName() +  " i=" + i);                     Thread.sleep(1000);                 }             }         } catch (InterruptedException e) {             e.printStackTrace();         }     }
      public static void main(String[] args) {         WaitDemo waitDemo = new WaitDemo();         Thread threadA = new Thread(new Runnable() {             @Override             public void run() {                 waitDemo.method1();             }         }, "Thread-A");         threadA.start();
                   try {             Thread.sleep(1000);         } catch (InterruptedException e) {             e.printStackTrace();         }
          Thread threadB = new Thread(new Runnable() {             @Override             public void run() {                 waitDemo.method2();             }         }, "Thread-B");         threadB.start();
      } }
 
  |