一、前言
线程通信是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();
} }
|