一道面试中常考的并发编程的代码题:
- 三个线程T1、T2、T3轮流打印ABC,打印n次,如ABCABCABCABC…….
- 两个线程交替打印1-100的奇偶数
- N个线程循环打印1-100
- ……
其实这类问题本质上都是线程通信问题,思路基本上都是一个线程执行完毕,阻塞该线程,唤醒其他线程,按顺序执行下一个线程。下面先来看最简单的,如何按顺序执行三个线程。
synchronized+wait/notify
基本思路就是线程A、线程B、线程C三个线程同时启动,因为变量num
的初始值为0
,所以线程B或线程C拿到锁后,进入while()
循环,然后执行wait()
方法,线程线程阻塞,释放锁。只有线程A拿到锁后,不进入while()
循环,执行num++
,打印字符A
,最后唤醒线程B和线程C。此时num
值为1
,只有线程B拿到锁后,不被阻塞,执行num++
,打印字符B
,最后唤醒线程A和线程C,后面以此类推。
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
| public class Wait_Notify_ABC { private volatile int num; private static final Object Lock = new Object();
private void printABC(int tagertNum){ for(int i = 0; i < 10; ++i){ synchronized (Lock){ while (num % 3 != tagertNum){ try { Lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num++; System.out.println(Thread.currentThread().getName()); Lock.notifyAll(); } } } public static void main(String[] args) { Wait_Notify_ABC wait_Notify_ABC = new Wait_Notify_ABC(); Thread threadA = new Thread(() -> wait_Notify_ABC.printABC(0), "A"); Thread threadB = new Thread(() -> wait_Notify_ABC.printABC(1), "B"); Thread threadC = new Thread(() -> wait_Notify_ABC.printABC(2), "C"); threadA.start(); threadB.start(); threadC.start(); } }
|
输出结果:
1 2
| ABCABCABCABCABCABCABCABCABCABC Process finished with exit code 0
|
下面看第二个问题,两个线程交替打印1-100的奇偶数,为了减少输入所占篇幅,这里将100 改成了10。基本思路上面类似,线程odd先拿到锁——打印数字——唤醒线程even——阻塞线程odd,以此循环。
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
| class Wait_Notify_Odd_Even{ private Object monitor = new Object(); private volatile int count; Wait_Notify_Odd_Even(int initCount) { this.count = initCount; } private void printOddEven() { synchronized (monitor) { while (count < 10) { try { System.out.print( Thread.currentThread().getName() + ":"); System.out.println(++count); monitor.notifyAll(); monitor.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } monitor.notifyAll(); } } public static void main(String[] args) throws InterruptedException { Wait_Notify_Odd_Even waitNotifyOddEven = new Wait_Notify_Odd_Even(0); new Thread(waitNotifyOddEven::printOddEven, "odd").start(); Thread.sleep(10); new Thread(waitNotifyOddEven::printOddEven, "even").start(); } }
|
运行结果:
1 2 3 4 5 6 7 8 9 10
| odd:1 even:2 odd:3 even:4 odd:5 even:6 odd:7 even:8 odd:9 even:10
|
再看第三个问题,N个线程循环打印1-100,其实仔细想想这个和三个线程循环打印ABC并没有什么本质区别,只需要加上判断是否到了打印数字的最大值的语句即可。假设N=3,为了能把输出结果完全显示,打印1-10,代码如下:
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
| class Wait_Notify_100 { private int num; private static final Object LOCK = new Object(); private int maxnum = 10; private void printABC(int targetNum) { while (true) { synchronized (LOCK) { while (num % 3 != targetNum) { if(num >= maxnum){ break; } try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if(num >= maxnum){ break; } num++; System.out.println(Thread.currentThread().getName() + ": " + num); LOCK.notifyAll(); } } } public static void main(String[] args) { Wait_Notify_100 wait_notify_100 = new Wait_Notify_100 (); new Thread(() -> { wait_notify_100.printABC(0); }, "thread1").start(); new Thread(() -> { wait_notify_100.printABC(1); }, "thread2").start(); new Thread(() -> { wait_notify_100.printABC(2); }, "thread3").start(); } }
|
输出打印:
1 2 3 4 5 6 7 8 9 10
| thread1: 1 thread2: 2 thread3: 3 thread1: 4 thread2: 5 thread3: 6 thread1: 7 thread2: 8 thread3: 9 thread1: 10
|
Lock
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
| class Lock_ABC {
private int num; private Lock lock = new ReentrantLock();
private void printABC(int targetNum) { for (int i = 0; i < 10; ) { lock.lock(); if (num % 3 == targetNum) { num++; i++; System.out.print(Thread.currentThread().getName()); } lock.unlock(); } }
public static void main(String[] args) { Lock_ABC lockABC = new Lock_ABC();
new Thread(() -> { lockABC.printABC(0); }, "A").start();
new Thread(() -> { lockABC.printABC(1); }, "B").start(); new Thread(() -> { lockABC.printABC(2); }, "C").start(); } }
|
输出日志:
1
| ABCABCABCABCABCABCABCABCABCABC
|
Lock+Condition
使用Lock+Condition实现对线程的精准唤醒,减少对同步锁的无意义竞争,浪费资源。
该思路和synchronized+wait/notify方法的很像,synchronized对应lock,await/signal方法对应wait/notify方法。下面的代码为了能精准地唤醒下一个线程,创建了多个Condition对象。
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
| class LockConditionABC {
private int num; private static Lock lock = new ReentrantLock(); private static Condition c1 = lock.newCondition(); private static Condition c2 = lock.newCondition(); private static Condition c3 = lock.newCondition();
private void printABC(int targetNum, Condition currentThread, Condition nextThread) { for (int i = 0; i < 10; ) { lock.lock(); try { while (num % 3 != targetNum) { currentThread.await(); } num++; i++; System.out.print(Thread.currentThread().getName()); nextThread.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
public static void main(String[] args) { LockConditionABC print = new LockConditionABC(); new Thread(() -> { print.printABC(0, c1, c2); }, "A").start(); new Thread(() -> { print.printABC(1, c2, c3); }, "B").start(); new Thread(() -> { print.printABC(2, c3, c1); }, "C").start(); } }
|
输出结果:
1
| ABCABCABCABCABCABCABCABCABCABC
|