java并发编程--一道经典多线程题的2种解法
問題的描述
啟動3個線程打印遞增的數字, 線程1先打印1,2,3,4,5, 然后是線程2打印6,7,8,9,10, 然后是線程3打印11,12,13,14,15. 接著再由線程1打印16,17,18,19,20....以此類推, 直到打印到75. 程序的輸出結果應該為:
?
線程1: 1
線程1: 2
線程1: 3
線程1: 4
線程1: 5
?
線程2: 6
線程2: 7
線程2: 8
線程2: 9
線程2: 10
...
?
線程3: 71
線程3: 72
線程3: 73
線程3: 74
線程3: 75
?
?
解法一: 采用原始的synchronized, wait(), notify(), notifyAll()等方式控制線程.
public class NumberPrintDemo {?? ?// n為即將打印的數字
?? ?private static int n = 1;
?? ?// state=1表示將由線程1打印數字, state=2表示將由線程2打印數字, state=3表示將由線程3打印數字
?? ?private static int state = 1;
?? ?public static void main(String[] args) {
?? ??? ?final NumberPrintDemo pn = new NumberPrintDemo();
?? ??? ?new Thread(new Runnable() {
?? ??? ??? ?public void run() {
?? ??? ??? ??? ?// 3個線程打印75個數字, 單個線程每次打印5個連續數字, 因此每個線程只需執行5次打印任務. 3*5*5=75
?? ??? ??? ??? ?for (int i = 0; i < 5; i++) {
?? ??? ??? ??? ??? ?// 3個線程都使用pn對象做鎖, 以保證每個交替期間只有一個線程在打印
?? ??? ??? ??? ??? ?synchronized (pn) {
?? ??? ??? ??? ??? ??? ?// 如果state!=1, 說明此時尚未輪到線程1打印, 線程1將調用pn的wait()方法, 直到下次被喚醒
?? ??? ??? ??? ??? ??? ?while (state != 1)
?? ??? ??? ??? ??? ??? ??? ?try {
?? ??? ??? ??? ??? ??? ??? ??? ?pn.wait();
?? ??? ??? ??? ??? ??? ??? ?} catch (InterruptedException e) {
?? ??? ??? ??? ??? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?// 當state=1時, 輪到線程1打印5次數字
?? ??? ??? ??? ??? ??? ?for (int j = 0; j < 5; j++) {
?? ??? ??? ??? ??? ??? ??? ?// 打印一次后n自增
?? ??? ??? ??? ??? ??? ??? ?System.out.println(Thread.currentThread().getName()
?? ??? ??? ??? ??? ??? ??? ??? ??? ?+ ": " + n++);
?? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?System.out.println();
?? ??? ??? ??? ??? ??? ?// 線程1打印完成后, 將state賦值為2, 表示接下來將輪到線程2打印
?? ??? ??? ??? ??? ??? ?state = 2;
?? ??? ??? ??? ??? ??? ?// notifyAll()方法喚醒在pn上wait的線程2和線程3, 同時線程1將退出同步代碼塊, 釋放pn鎖.
?? ??? ??? ??? ??? ??? ?// 因此3個線程將再次競爭pn鎖
?? ??? ??? ??? ??? ??? ?// 假如線程1或線程3競爭到資源, 由于state不為1或3, 線程1或線程3將很快再次wait, 釋放出剛到手的pn鎖.
?? ??? ??? ??? ??? ??? ?// 只有線程2可以通過state判定, 所以線程2一定是執行下次打印任務的線程.
?? ??? ??? ??? ??? ??? ?// 對于線程2來說, 獲得鎖的道路也許是曲折的, 但前途一定是光明的.
?? ??? ??? ??? ??? ??? ?pn.notifyAll();
?? ??? ??? ??? ??? ?}
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ?}, "線程1").start();
?? ??? ?new Thread(new Runnable() {
?? ??? ??? ?public void run() {
?? ??? ??? ??? ?for (int i = 0; i < 5; i++) {
?? ??? ??? ??? ??? ?synchronized (pn) {
?? ??? ??? ??? ??? ??? ?while (state != 2)
?? ??? ??? ??? ??? ??? ??? ?try {
?? ??? ??? ??? ??? ??? ??? ??? ?pn.wait();
?? ??? ??? ??? ??? ??? ??? ?} catch (InterruptedException e) {
?? ??? ??? ??? ??? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?for (int j = 0; j < 5; j++) {
?? ??? ??? ??? ??? ??? ??? ?System.out.println(Thread.currentThread().getName()
?? ??? ??? ??? ??? ??? ??? ??? ??? ?+ ": " + n++);
?? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?System.out.println();
?? ??? ??? ??? ??? ??? ?state = 3;
?? ??? ??? ??? ??? ??? ?pn.notifyAll();
?? ??? ??? ??? ??? ?}
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ?}, "線程2").start();
?? ??? ?new Thread(new Runnable() {
?? ??? ??? ?public void run() {
?? ??? ??? ??? ?for (int i = 0; i < 5; i++) {
?? ??? ??? ??? ??? ?synchronized (pn) {
?? ??? ??? ??? ??? ??? ?while (state != 3)
?? ??? ??? ??? ??? ??? ??? ?try {
?? ??? ??? ??? ??? ??? ??? ??? ?pn.wait();
?? ??? ??? ??? ??? ??? ??? ?} catch (InterruptedException e) {
?? ??? ??? ??? ??? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?for (int j = 0; j < 5; j++) {
?? ??? ??? ??? ??? ??? ??? ?System.out.println(Thread.currentThread().getName()
?? ??? ??? ??? ??? ??? ??? ??? ??? ?+ ": " + n++);
?? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?System.out.println();
?? ??? ??? ??? ??? ??? ?state = 1;
?? ??? ??? ??? ??? ??? ?pn.notifyAll();
?? ??? ??? ??? ??? ?}
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ?}, "線程3").start();
?? ?}
}
解法二: 采用JDK1.5并發包提供的Lock, Condition等類的相關方法控制線程.
?public class NumberPrint implements Runnable {
?? ?private int state = 1;
?? ?private int n = 1;
?? ?// 使用lock做鎖
?? ?private ReentrantLock lock = new ReentrantLock();
?? ?// 獲得lock鎖的3個分支條件
?? ?private Condition c1 = lock.newCondition();
?? ?private Condition c2 = lock.newCondition();
?? ?private Condition c3 = lock.newCondition();
?? ?@Override
?? ?public void run() {
?? ??? ?new Thread(new Runnable() {
?? ??? ??? ?public void run() {
?? ??? ??? ??? ?for (int i = 0; i < 5; i++) {
?? ??? ??? ??? ??? ?try {
?? ??? ??? ??? ??? ??? ?// 線程1獲得lock鎖后, 其他線程將無法進入需要lock鎖的代碼塊.
?? ??? ??? ??? ??? ??? ?// 在lock.lock()和lock.unlock()之間的代碼相當于使用了synchronized(lock){}
?? ??? ??? ??? ??? ??? ?lock.lock();
?? ??? ??? ??? ??? ??? ?while (state != 1)
?? ??? ??? ??? ??? ??? ??? ?try {
?? ??? ??? ??? ??? ??? ??? ??? ?// 線程1競爭到了lock, 但是發現state不為1, 說明此時還未輪到線程1打印.
?? ??? ??? ??? ??? ??? ??? ??? ?// 因此線程1將在c1上wait
?? ??? ??? ??? ??? ??? ??? ??? ?// 與解法一不同的是, 三個線程并非在同一個對象上wait, 也不由同一個對象喚醒
?? ??? ??? ??? ??? ??? ??? ??? ?c1.await();
?? ??? ??? ??? ??? ??? ??? ?} catch (InterruptedException e) {
?? ??? ??? ??? ??? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?// 如果線程1競爭到了lock, 也通過了state判定, 將執行打印任務
?? ??? ??? ??? ??? ??? ?for (int j = 0; j < 5; j++) {
?? ??? ??? ??? ??? ??? ??? ?System.out.println(Thread.currentThread().getName()
?? ??? ??? ??? ??? ??? ??? ??? ??? ?+ ": " + n++);
?? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?System.out.println();
?? ??? ??? ??? ??? ??? ?// 打印完成后將state賦值為2, 表示下一次的打印任務將由線程2執行
?? ??? ??? ??? ??? ??? ?state = 2;
?? ??? ??? ??? ??? ??? ?// 喚醒在c2分支上wait的線程2
?? ??? ??? ??? ??? ??? ?c2.signal();
?? ??? ??? ??? ??? ?} finally {
?? ??? ??? ??? ??? ??? ?// 打印任務執行完成后需要確保鎖被釋放, 因此將釋放鎖的代碼放在finally中
?? ??? ??? ??? ??? ??? ?lock.unlock();
?? ??? ??? ??? ??? ?}
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ?}, "線程1").start();
?? ??? ?new Thread(new Runnable() {
?? ??? ??? ?public void run() {
?? ??? ??? ??? ?for (int i = 0; i < 5; i++) {
?? ??? ??? ??? ??? ?try {
?? ??? ??? ??? ??? ??? ?lock.lock();
?? ??? ??? ??? ??? ??? ?while (state != 2)
?? ??? ??? ??? ??? ??? ??? ?try {
?? ??? ??? ??? ??? ??? ??? ??? ?c2.await();
?? ??? ??? ??? ??? ??? ??? ?} catch (InterruptedException e) {
?? ??? ??? ??? ??? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?for (int j = 0; j < 5; j++) {
?? ??? ??? ??? ??? ??? ??? ?System.out.println(Thread.currentThread().getName()
?? ??? ??? ??? ??? ??? ??? ??? ??? ?+ ": " + n++);
?? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?System.out.println();
?? ??? ??? ??? ??? ??? ?state = 3;
?? ??? ??? ??? ??? ??? ?c3.signal();
?? ??? ??? ??? ??? ?} finally {
?? ??? ??? ??? ??? ??? ?lock.unlock();
?? ??? ??? ??? ??? ?}
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ?}, "線程2").start();
?? ??? ?new Thread(new Runnable() {
?? ??? ??? ?public void run() {
?? ??? ??? ??? ?for (int i = 0; i < 5; i++) {
?? ??? ??? ??? ??? ?try {
?? ??? ??? ??? ??? ??? ?lock.lock();
?? ??? ??? ??? ??? ??? ?while (state != 3)
?? ??? ??? ??? ??? ??? ??? ?try {
?? ??? ??? ??? ??? ??? ??? ??? ?c3.await();
?? ??? ??? ??? ??? ??? ??? ?} catch (InterruptedException e) {
?? ??? ??? ??? ??? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?for (int j = 0; j < 5; j++) {
?? ??? ??? ??? ??? ??? ??? ?System.out.println(Thread.currentThread().getName()
?? ??? ??? ??? ??? ??? ??? ??? ??? ?+ ": " + n++);
?? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?System.out.println();
?? ??? ??? ??? ??? ??? ?state = 1;
?? ??? ??? ??? ??? ??? ?c1.signal();
?? ??? ??? ??? ??? ?} finally {
?? ??? ??? ??? ??? ??? ?lock.unlock();
?? ??? ??? ??? ??? ?}
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ?}, "線程3").start();
?? ?}
?? ?
?? ?public static void main(String[] args) {
?? ??? ?new NumberPrint().run();
?? ?}
}
總結: 對比解法一和解法二, 顯然解法二是更好的解決方案. 解法一的問題在于無法進行精確喚醒, 比如線程1執行完打印任務并調用pn.notifyAll()方法后, 3個線程將再次競爭鎖, 而不是精確喚醒線程2. 雖然線程2最終將贏得鎖, 下一次的打印任務也肯定會由線程2執行, 但是競爭的持續時間是不可預知的, 只能看線程2的人品.
最糟糕的情形可以是: 線程3競爭到了鎖, 緊接著wait. 接下來線程1也競爭到了鎖, 然后線程1也wait. 此時就再也沒有其他線程跟線程2競爭了, 線程2終于艱難的贏得了鎖...
?
留下3個問題供有興趣的朋友思考:
1. 解法一和解法二中的while (state != xx)是否可以換成if(state != xx), 為什么?
2. 解法一的中的pn.notifyAll()是否可以換成pn.notify(), 為什么?
3. 是否可以用wait(), notify(), notifyAll()等方法完成類似解法二的精確喚醒, 請給出方案或代碼.--這個問題我思考了很久, 卻沒有頭緒. 關鍵的困難在于必須調用pn的wait()方法和notifyAll()方法, 而不能是其他對象的wait()和notifyAll()方法.
總結
以上是生活随笔為你收集整理的java并发编程--一道经典多线程题的2种解法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员应该知道的二十三种设计模式
- 下一篇: 我应该做的更差吗?