java多線程編程之連續打印abc的幾種解法

一道編程題以下:java

實例化三個線程,一個線程打印a,一個線程打印b,一個線程打印c,三個線程同時執行,要求打印出10個連着的abc。編程

題目分析:安全

經過題意咱們能夠得出,本題須要咱們使用三個線程,三個線程分別會打印6次字符,關鍵是如何保證順序必定是abc...呢。因此此題須要同步機制來解決問題!多線程

令打印字符A的線程爲ThreadA,打印B的ThreadB,打印C的爲ThreadC。問題爲三線程間的同步喚醒操做,主要的目的就是使程序按ThreadA->ThreadB->ThreadC->ThreadA循環執行三個線程,所以本人整理出了三種方式來解決此問題。ide

最初給個錯誤示例:函數

public class MyABC {
	public static void main(String[] args) {
		MyRun run = new MyRun();
		Thread a = new Thread(run, "A");
		Thread b = new Thread(run, "B");
		Thread c = new Thread(run, "C");
		a.start();
		b.start();
		c.start();
	}
}

class MyRun implements Runnable {

	private int total = 1;
	private boolean goA = true;
	private boolean goB = false;
	private boolean goC = false;

	@Override
	public void run() {
		while (total <= 10) {
			if (Thread.currentThread().getName().equals("A") && goA) {
				System.out.print(Thread.currentThread().getName());
				// 可試着用sleep來模擬CPU分配時間,看看是什麼結果
//				Thread.currentThread().sleep(100);
//				synchronized (this) {
					goB = true;
					goA = false;
//				}
			} else if (Thread.currentThread().getName().equals("B") && goB) {
				System.out.print(Thread.currentThread().getName());
				// 可試着用sleep來模擬CPU分配時間,看看是什麼結果
//				Thread.currentThread().sleep(100);
					goC = true;
					goB = false;
			} else if (Thread.currentThread().getName().equals("C") && goC) {
				System.out.print(Thread.currentThread().getName());
				// 可試着用sleep來模擬CPU分配時間,看看是什麼結果
//				Thread.currentThread().sleep(100);
				goA = true;
				goC = false;
				total++;
			}
		}
	}
}

解析:性能

該代碼嚴格依賴各個標誌,一旦標誌變換順序被打亂,程序就崩了。線程的執行是由系統隨機調度的,也就是當線程A執行完gob=true時,頗有可能CPU被系統收回,此時B執行,C執行,當C執行到goa=true時,假設系統把C的CPU收回,分配給A,而A獲得CPU權限後繼續下一條代碼goa=false;此時標誌位亂了,程序沒法繼續正常執行。由於線程是隨機的,全部還有不少可能。。。別的不說,最少要在兩個狀態那裏加上synchronized 包裹this

下面給出正確的作法:atom

1、經過兩個鎖(不推薦,可讀性和安全性比較差)spa

(1)解法1:

/**
 * 基於兩個lock實現連續打印abcabc....
 * @author lixiaoxi
 *
 */
public class TwoLockPrinter implements Runnable {

    // 打印次數
    private static final int PRINT_COUNT = 10;
    // 前一個線程的打印鎖
    private final Object fontLock;
    // 本線程的打印鎖
    private final Object thisLock;
    // 打印字符
    private final char printChar;

    public TwoLockPrinter(Object fontLock, Object thisLock, char printChar) {
        this.fontLock = fontLock;
        this.thisLock = thisLock;
        this.printChar = printChar;
    }

    @Override
    public void run() {
        // 連續打印PRINT_COUNT次
        for (int i = 0; i < PRINT_COUNT; i++) {
            // 獲取前一個線程的打印鎖
            synchronized (fontLock) {
                // 獲取本線程的打印鎖
                synchronized (thisLock) {
                    //打印字符
                    System.out.print(printChar);
                    // 經過本線程的打印鎖喚醒後面的線程 
                    // notify和notifyall都可,由於同一時刻只有一個線程在等待(由於每一個線程只有自身的鎖和上一個鎖,不會出現a,b,c同時搶一個鎖的狀況)
                    thisLock.notify();
                }
                // 不是最後一次則經過fontLock等待被喚醒
                // 必需要加判斷,否則雖然可以打印10次,但10次後就會直接死鎖
                //最後一次打印的時候線程A喚醒線程B,A線程等待;線程B喚醒線程C,線程B等待;線程C喚醒線程A,線程C等待(此時只有A線程不會被阻塞,線程BC會被阻塞)
                if(i < PRINT_COUNT - 1){
                    try {
                        // 經過fontLock等待被喚醒
                        fontLock.wait();
                        
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //若是不加if(i < PRINT_COUNT - 1)的條件能夠這麼作:由於線程A不阻塞,而線程B在等待A鎖,線程C在等待B鎖,因此這裏能夠經過線程A的notify()釋放A鎖,來喚醒B線程
                synchronized (thisLock) {
                	thisLock.notify();
                }
            }    
        }    
    }

    public static void main(String[] args) throws InterruptedException {
        // 打印A線程的鎖
        Object lockA = new Object();
        // 打印B線程的鎖
        Object lockB = new Object();
        // 打印C線程的鎖
        Object lockC = new Object();
        
        // 打印a的線程
        Thread threadA = new Thread(new TwoLockPrinter(lockC, lockA, 'A'));
        // 打印b的線程
        Thread threadB = new Thread(new TwoLockPrinter(lockA, lockB, 'B'));
        // 打印c的線程
        Thread threadC = new Thread(new TwoLockPrinter(lockB, lockC, 'C'));

        // 依次開啓a b c線程
        threadA.start();
        Thread.sleep(100); // 確保按順序A、B、C執行
        threadB.start();
        Thread.sleep(100);
        threadC.start();
        Thread.sleep(100);
    }
}

打印結果:

ABCABCABCABCABCABCABCABCABCABC

分析:

    此解法爲了爲了肯定喚醒、等待的順序,每個線程必須同時持有兩個對象鎖,才能繼續執行。一個對象鎖是fontLock,就是前一個線程所持有的對象鎖,還有一個就是自身對象鎖thisLock。主要的思想就是,爲了控制執行的順序,必需要先持有fontLock鎖,也就是前一個線程要釋放掉前一個線程自身的對象鎖,當前線程再去申請自身對象鎖,二者兼備時打印,以後首先調用thisLock.notify()釋放自身對象鎖,喚醒下一個等待線程,再調用fontLock.wait()釋放prev對象鎖,暫停當前線程,等待再次被喚醒後進入循環。運行上述代碼,能夠發現三個線程循環打印ABC,共10次。程序運行的主要過程就是A線程最早運行,持有C,A對象鎖,後釋放A鎖,喚醒B。線程B等待A鎖,再申請B鎖,後打印B,再釋放B鎖,喚醒C,線程C等待B鎖,再申請C鎖,後打印C,再釋放C鎖,喚醒A。看起來彷佛沒什麼問題,但若是你仔細想一下,就會發現有問題,就是初始條件,三個線程按照A,B,C的順序來啓動,按照前面的思考,A喚醒B,B喚醒C,C再喚醒A。可是這種假設依賴於JVM中線程調度、執行的順序,因此須要手動控制他們三個的啓動順序,即Thread.Sleep(100)。

通俗點說:就是每一個對象只有一把鎖,即lockA 一把鎖,lockB一把鎖,lockC一把鎖,其中想進入同步代碼塊,執行打印,必需要具備兩把鎖,即threadA要執行打印,要具有lockC和lockA,threadB要執行打印,必須具有lockA和lockB,threadC要具有lockB和lockC,並且獲取兩把鎖的順序要一致,即獲取到第一把鎖以後,才能去爭取第二把鎖(若是不能獲取第一把鎖,那麼取爭取第二把鎖的機會也沒有,就不能執行run方法),線程A啓動以後,在main主線程執行sleep休眠以後,得到cpu時間片開始執行run方法,此時得到lockC以後,再得到lockA,進入同步代碼塊,此時執行threadA執行thisLock.notify();喚醒外面等待的lockA鎖的線程(這個時候B線程尚未執行,因此外面沒有線程等待lockA鎖,若是B線程啓動以後,在lockA鎖池中等待,那麼執行notify()方法,會當即喚醒threadB線程,去參與lockA鎖的爭奪,可是notify()只是喚醒,並不會釋放鎖(只有在執行完synchronized (thisLock)的代碼以後,才能釋放thisLock的鎖,很明顯執行了thisLock.notify(),synchronized (thisLock)鎖住的代碼就已經結束,因此thisLock.notify()會當即喚醒B線程,同時釋放lockB的鎖),只有在threadA調用fontLock.wait()以後(線程執行完同步代碼塊也會釋放鎖),會當即釋放自身持有的lockC鎖(可是此時lockA鎖並無釋放),並進入等待,等待持有lockC鎖的線程的喚醒,即threadC線程,其餘代碼執行過程分析省略...

注意:一個線程經過synchronized嵌套鎖住多個對象,而後在最裏層調用wait()函數,只釋放wait()函數關聯的鎖對象,而不是釋放線程當時持有的所有鎖(對象的wait()函數,只會釋放該對象的鎖,不影響其餘的鎖的狀態)。

(2)解法2:

/**
 * 基於兩個lock實現連續打印abcabc....
 * @author lixiaoxi
 *
 */
public class MyThreadPrinter2 implements Runnable {

	private String name;
	private Object prev;
	private Object self;

	private MyThreadPrinter2(String name, Object prev, Object self) {
		this.name = name;
		this.prev = prev;
		this.self = self;
	}

	@Override
	public void run() {
		int count = 10;
		while (count > 0) {
			synchronized (prev) {
				synchronized (self) {
					System.out.print(name);
					count--;
					//通知等待self鎖的線程能夠搶鎖了,即喚醒等待self鎖的線程,即後一個線程,但此時並無釋放self的鎖,只有執行完synchronized (self) {...}代碼後纔會釋放self的鎖
					self.notify();
				}
				try {
					prev.wait();//當前線程持有的prev的鎖被釋放,同時當前線程進入等待被喚醒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		}

		//若是缺失下面這段代碼,那麼會有兩個線程沒法退出,由於輸出A的線程打印10次後,沒辦法調用notify()通知打印B的線程,以此內推
//		synchronized (self) {
//			/**
//			  *線程A被喚醒,進入最後一次打印A的時候,self.notify()會喚醒等待A鎖(self)的線程(即B線程),經過prev.wait()會釋放線程A持有的C鎖,當前線程A等待被喚醒
//			  *線程B被喚醒,進入最後一次打印B的時候,self.notify()會喚醒等待B鎖(self)的線程(即C線程),經過prev.wait()會釋放線程B持有的A鎖,此時線程B等待被喚醒
//			  *線程C被喚醒,進入最後一次打印C的時候,self.notify()會喚醒等待C鎖(self)的線程(即A線程),經過prev.wait()會釋放線程C持有的B鎖,此時線程C等待被喚醒
//			 *總結:若是缺失這段代碼,線程A會被喚醒,可是BC會阻塞,造成死鎖
//		        *加入這段代碼的效果是:最後一次線程C在執行了prev.wait()以後,會釋放自身持有的A鎖,此時線程A被喚醒(B,C在等待被喚醒),繼續執行,進入到while(){}下面的synchronized (self) {}
//		        *此時經過self.notify();會喚醒等待A鎖的線程(B線程),在線程A執行完run()方法後,A線程會釋放自身持有的A鎖,此時B線程搶到A鎖,繼續執行while後的同步代碼塊,同理B線程會喚醒C線程
//			 */
//			self.notify();
//		}
//		System.out.print(Thread.currentThread().getName()+"---執行完畢");
	}

	public static void main(String[] args) throws Exception {
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
		MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
		MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);

		new Thread(pa).start();
		Thread.sleep(10);  
		new Thread(pb).start();
		Thread.sleep(10);  
		new Thread(pc).start();
	}
}

先來解釋一下其總體思路,從大的方向上來說,該問題爲三線程間的同步喚醒操做,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循環執行三個線程。

爲了控制線程執行的順序,那麼就必需要肯定喚醒、等待的順序,因此每個線程必須同時持有兩個對象鎖,才能繼續執行。

一個對象鎖是prev,就是前一個線程所持有的對象鎖。還有一個就是自身對象鎖。主要的思想就是,爲了控制執行的順序,必需要先持有prev鎖,也就前一個線程要釋放自身對象鎖,再去申請自身對象鎖,二者兼備時打印,以後首先調用self.notify()釋放自身對象鎖,喚醒下一個等待線程,再調用prev.wait()釋放prev對象鎖,終止當前線程,等待循環結束後再次被喚醒。

運行上述代碼,能夠發現三個線程循環打印ABC,共10次。程序運行的主要過程就是A線程最早運行,持有C,A對象鎖,後釋放A,C鎖,喚醒B。線程B等待A鎖,再申請B鎖,後打印B,再釋放B,A鎖,喚醒C,線程C等待B鎖,再申請C鎖,後打印C,再釋放C,B鎖,喚醒A。

看起來彷佛沒什麼問題,但若是你仔細想一下,就會發現有問題,就是初始條件,三個線程按照A,B,C的順序來啓動,按照前面的思考,A喚醒B,B喚醒C,C再喚醒A。

可是這種假設依賴於JVM中線程調度、執行的順序。具體來講就是,在main主線程啓動ThreadA後,須要在ThreadA執行完,在prev.wait()等待時,再切回線程啓動ThreadB,ThreadB執行完,在prev.wait()等待時,再切回主線程,啓動ThreadC,只有JVM按照這個線程運行順序執行,才能保證輸出的結果是正確的。而這依賴於JVM的具體實現。

考慮一種狀況,以下:若是主線程在啓動A後,在執行A的同步代碼塊的過程當中又切回主線程(假設在代碼self.notify以前就被切回主線程),啓動了ThreadB,ThreadC,以後,因爲ThreadA還沒有執行self.notify,也就是ThreadB須要在synchronized(prev)處等待,而ThreadB由於等待prev的鎖(A鎖),而沒法進入synchronized (self),因此線程B此時沒有持有自身的B鎖(self),而這時C卻調用synchronized(prev)獲取了對b的對象鎖。這樣,在A調用完後,同時ThreadB獲取了prev也就是a的對象鎖,而ThreadC持有B鎖,ThreadA也釋放了C鎖,此時ThreadC的執行條件就已經知足了,會打印C,以後釋放c,及b的對象鎖,這時ThreadB具有了運行條件,會打印B,也就是循環變成了ACBACB了。這種狀況,能夠經過在run中主動釋放CPU,來進行模擬(即在self.notify以前使用Thread.sleep(1)便可讓出cpu時間片,讓其餘線程執行)。爲了不這種與JVM調度有關的不肯定性。須要讓A,B,C三個線程以肯定的順序啓動,在線程A和線程B啓動以後,各加入:Thread.sleep(10); 

(3)用concurrent包實現的一樣的邏輯代碼

/**
 * 基於兩個lock實現連續打印abcabc.... concurrent的邏輯代碼
 * 
 * @author lixiaoxi
 *
 */
public class MyThreadPrinter2 implements Runnable {

	private String name;
	private ReentrantLock prev;
	private ReentrantLock self;
	private Condition prevCondition;
	private Condition selfCondition;

	private MyThreadPrinter2(String name, ReentrantLock prev, Condition prevCondition, ReentrantLock self,
			Condition selfCondition) {
		this.name = name;
		this.prev = prev;
		this.self = self;
		this.prevCondition = prevCondition;
		this.selfCondition = selfCondition;
	}

	public void run() {
		int count = 10;
		while (count > 0) {
			prev.lock();
			self.lock();
			System.out.print(name);
			count--;
			selfCondition.signal();
			self.unlock();
			try {
				prevCondition.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				prev.unlock();
			}
		}
		self.lock();
		selfCondition.signal();
		self.unlock();
	}

	public static void main(String[] args) throws Exception {
		ReentrantLock a = new ReentrantLock();
		Condition aCondition = a.newCondition();
		ReentrantLock b = new ReentrantLock();
		Condition bCondition = b.newCondition();
		ReentrantLock c = new ReentrantLock();
		Condition cCondition = c.newCondition();
		MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, cCondition, a, aCondition);
		MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, aCondition, b, bCondition);
		MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, bCondition, c, cCondition);

		new Thread(pa).start();
		Thread.sleep(10);
		new Thread(pb).start();
		Thread.sleep(10);
		new Thread(pc).start();
		Thread.sleep(10);
	}
}

2、經過一個ReentrantLock和三個conditon實現(推薦,安全性,性能和可讀性較高)

package com.demo.test;

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

/**
 * 基於一個ReentrantLock和三個conditon實現連續打印abcabc...
 * @author lixiaoxi
 *
 */
public class RcSyncPrinter implements Runnable{

    // 打印次數
    private static final int PRINT_COUNT = 10;
    // 打印鎖
    private final ReentrantLock reentrantLock;
    // 本線程打印所需的condition
    private final Condition thisCondtion;
    // 下一個線程打印所須要的condition
    private final Condition nextCondtion;
    // 打印字符
    private final char printChar;

    public RcSyncPrinter(ReentrantLock reentrantLock, Condition thisCondtion, Condition nextCondition, 
            char printChar) {
        this.reentrantLock = reentrantLock;
        this.nextCondtion = nextCondition;
        this.thisCondtion = thisCondtion;
        this.printChar = printChar;
    }

    @Override
    public void run() {
        // 獲取打印鎖 進入臨界區
        reentrantLock.lock();
        try {
            // 連續打印PRINT_COUNT次
            for (int i = 0; i < PRINT_COUNT; i++) {
                //打印字符
                System.out.print(printChar);
                // 使用nextCondition喚醒下一個線程
                // 由於只有一個線程在等待,因此signal或者signalAll均可以
                nextCondtion.signal();
                // 不是最後一次則經過thisCondtion等待被喚醒
                // 必需要加判斷,否則雖然可以打印10次,但10次後就會直接死鎖
                if (i < PRINT_COUNT - 1) {
                    try {
                        // 本線程讓出鎖並等待喚醒
                        thisCondtion.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        } finally {
            // 釋放打印鎖
            reentrantLock.unlock();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        // 寫鎖
        ReentrantLock lock = new ReentrantLock();
        // 打印a線程的condition
        Condition conditionA = lock.newCondition();
        // 打印b線程的condition
        Condition conditionB = lock.newCondition();
        // 打印c線程的condition
        Condition conditionC = lock.newCondition();
        // 實例化A線程
        Thread printerA = new Thread(new RcSyncPrinter(lock, conditionA, conditionB, 'A'));
        // 實例化B線程
        Thread printerB = new Thread(new RcSyncPrinter(lock, conditionB, conditionC, 'B'));
        // 實例化C線程
        Thread printerC = new Thread(new RcSyncPrinter(lock, conditionC, conditionA, 'C'));
        // 依次開始A B C線程
        printerA.start();
        Thread.sleep(100);
        //printerA.join();
        printerB.start();
        Thread.sleep(100);
        printerC.start();
    }
}

打印結果:

ABCABCABCABCABCABCABCABCABCABC

分析:

    仔細想一想本問題,既然同一時刻只能有一個線程打印字符,那咱們爲何不使用一個同步鎖ReentrantLock?線程之間的喚醒操做能夠經過Condition實現,且Condition能夠有多個,每一個condition.await阻塞只能經過該condition的signal/signalall來喚醒!這是synchronized關鍵字所達不到的,那咱們就能夠給每一個打印線程一個自身的condition和下一個線程的condition,每次打印字符後,調用下一個線程的condition.signal來喚醒下一個線程,而後自身再經過本身的condition.await來釋放鎖並等待喚醒。

注意:這裏不能使用join(),由於join()方法會讓主線程等待printerA線程執行完畢,而線程中有await()方法,會一直阻塞...

下面分析下線程啓動的過程:
調用start()方法後,線程printerA啓動,這時此線程處於就緒(可運行)狀態,並無運行,一旦獲得cpu時間片,就開始執行run()方法;此時主線程繼續向下執行, Thread.sleep(100)讓主線程阻塞此時main線程讓出CPU資源,printerA得到cpu時間片,開始執行run方法,調用reentrantLock.lock();獲取鎖資源,進入同步方法,在printerA執行到thisCondtion.await()的時候,釋放鎖資源並等待被喚醒;主線程main在sleep在休眠時間事後的某個點獲取cpu資源後,繼續向下執行,此時printerB線程啓動(此時若是printerA若是尚未執行到await()方法,則在等待;若是printerA已經執行了await(),則printerB獲取鎖資源,進入同步方法),如下C線程同理..
nextCondtion.signal();喚醒是由於等待了thisCondtion.await();若是不寫nextCondtion.signal(),那麼只要等待了就沒有喚醒的機會了...

3、經過一個鎖和一個狀態變量來實現(推薦)

package com.demo.test;

/**
 * 基於一個鎖和一個狀態變量實現連續打印abcabc...
 * @author lixiaoxi
 *
 */
public class StateLockPrinter {
    //狀態變量
    private volatile int state=0;
    
    // 打印線程
    private class Printer implements Runnable {
        //打印次數
        private static final int PRINT_COUNT=10;
        //打印鎖
        private final Object printLock;
        //打印標誌位 和state變量相關
        private final int printFlag;
        //後繼線程的線程的打印標誌位,state變量相關
        private final int nextPrintFlag;
        //該線程的打印字符
        private final char printChar;
        public Printer(Object printLock, int printFlag,int nextPrintFlag, char printChar) {
            super();
            this.printLock = printLock;
            this.printFlag=printFlag;
            this.nextPrintFlag=nextPrintFlag;
            this.printChar = printChar;
        }

        @Override
        public void run() {
            //獲取打印鎖 進入臨界區
            synchronized (printLock) {
                //連續打印PRINT_COUNT次
                for(int i=0;i<PRINT_COUNT;i++){
                    //循環檢驗標誌位 每次都阻塞而後等待喚醒
                    while (state!=printFlag) {
                        try {
                            printLock.wait();
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                    //打印字符
                    System.out.print(printChar);
                    //設置狀態變量爲下一個線程的標誌位
                    state=nextPrintFlag;
                    //注意要notifyall,否則會死鎖,由於notify只通知一個,
                    //可是同時等待的是兩個,若是喚醒的不是正確那個就會沒人喚醒,死鎖了
                    printLock.notifyAll();
                }
            }
        }
        
    }

    public void test() throws InterruptedException{
        //鎖
        Object lock=new Object();
        //打印A的線程
        Thread threadA=new Thread(new Printer(lock, 0,1, 'A'));
        //打印B的線程
        Thread threadB=new Thread(new Printer(lock, 1,2, 'B'));
        //打印C的線程
        Thread threadC=new Thread(new Printer(lock, 2,0, 'C'));
        //一次啓動A B C線程
        threadA.start();
        Thread.sleep(1000);
        threadB.start();
        Thread.sleep(1000);
        threadC.start();
    }
    
    public static void main(String[] args) throws InterruptedException {
        
        StateLockPrinter print = new StateLockPrinter();
        print.test();
    }
   
}

打印結果:

ABCABCABCABCABCABCABCABCABCABC

分析:

    狀態變量是一個volatile的整型變量,0表明打印a,1表明打印b,2表明打印c,三個線程都循環檢驗標誌位,經過阻塞前和阻塞後兩次判斷能夠確保當前打印的正確順序,隨後線程打印字符,而後設置下一個狀態字符,喚醒其它線程,而後從新進入循環。 

補充題

三個Java多線程循環打印遞增的數字,每一個線程打印5個數值,打印週期1-75,一樣的解法:

package com.demo.test;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 數字打印,三個線程同時打印數字,第一個線程打印12345,第二個線程打印678910 .........
 * @author lixiaoxi
 *
 */
public class NumberPrinter {

    //打印計數器
    private final AtomicInteger counter=new AtomicInteger(0);
    
    private class Printer implements Runnable{
        //總共須要打印TOTAL_PRINT_COUNT次
        private static final int TOTAL_PRINT_COUNT = 5;
        //每次打印PER_PRINT_COUNT次
        private static final int PER_PRINT_COUNT = 5;
        //打印鎖
        private final ReentrantLock reentrantLock;
        //前一個線程的condition
        private final Condition afterCondition;
        //本線程的condition
        private final Condition thisCondtion;
        
        public Printer(ReentrantLock reentrantLock, Condition thisCondtion,Condition afterCondition) {
            super();
            this.reentrantLock = reentrantLock;
            this.afterCondition = afterCondition;
            this.thisCondtion = thisCondtion;
        }

        @Override
        public void run() {
            //進入臨界區
            reentrantLock.lock();
            try {
                //循環打印TOTAL_PRINT_COUNT次
                for(int i=0;i<TOTAL_PRINT_COUNT;i++){
                    //打印操做
                    for(int j=0;j<PER_PRINT_COUNT;j++){
                        //以原子方式將當前值加 1。
                        //incrementAndGet返回的是新值(即加1後的值)
                        System.out.println(counter.incrementAndGet());
                    }
                    //經過afterCondition通知後面線程
                    afterCondition.signalAll();
                    if(i < TOTAL_PRINT_COUNT - 1){
                        try {
                            //本線程釋放鎖並等待喚醒
                            thisCondtion.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } finally {
                reentrantLock.unlock();
            }
        }
    }
    
    public void test() throws InterruptedException {
        //打印鎖
        ReentrantLock reentrantLock=new ReentrantLock();
        //打印A線程的Condition
        Condition conditionA=reentrantLock.newCondition();
        //打印B線程的Condition
        Condition conditionB=reentrantLock.newCondition();
        //打印C線程的Condition
        Condition conditionC=reentrantLock.newCondition();

        //打印線程A
        Thread threadA=new Thread(new Printer(reentrantLock,conditionA, conditionB));
        //打印線程B
        Thread threadB=new Thread(new Printer(reentrantLock, conditionB, conditionC));
        //打印線程C
        Thread threadC=new Thread(new Printer(reentrantLock, conditionC, conditionA));
        // 依次開啓a b c線程
        threadA.start();
        Thread.sleep(100);
        threadB.start();
        Thread.sleep(100);
        threadC.start();
    }
    
    public static void main(String[] args) throws InterruptedException {
        NumberPrinter print = new NumberPrinter();
        print.test();
    }
}

運行結果:

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
相關文章
相關標籤/搜索