三個線程交替順序打印ABC


題目描述

建立三個線程A、B、C,A線程打印10次字母A,B線程打印10次字母B,C線程打印10次字母C,但是要求三個線程同時運行,並且實現交替打印,即按照ABCABCABC的順序打印。

5種方法

  1. 使用synchronized, wait和notifyAll
  2. 使用Lock->ReentrantLock 和 state標志
  3. 使用Lock->ReentrantLock 和Condition(await 、signal、signalAll)
  4. 使用Semaphore
  5. 使用AtomicInteger

1、使用synchronized, wait和notifyAll

使用同步塊和wait、notify的方法控制三個線程的執行次序。具體方法如下:從大的方向上來講,該問題為三線程間的同步喚醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循環執行三個線程。為了控制線程執行的順序,那么就必須要確定喚醒、等待的順序,所以每一個線程必須同時持有兩個對象鎖,才能進行打印操作。一個對象鎖是prev,就是前一個線程所對應的對象鎖,其主要作用是保證當前線程一定是在前一個線程操作完成后(即前一個線程釋放了其對應的對象鎖)才開始執行。還有一個鎖就是自身對象鎖。主要的思想就是,為了控制執行的順序,必須要先持有prev鎖(也就前一個線程要釋放其自身對象鎖),然后當前線程再申請自己對象鎖,兩者兼備時打印。之后首先調用self.notify()喚醒下一個等待線程(注意notify不會立即釋放對象鎖,只有等到同步塊代碼執行完畢后才會釋放),再調用prev.wait()立即釋放prev對象鎖,當前線程進入休眠,等待其他線程的notify操作再次喚醒。


package com.beike.offer; public class Synchronized_ABC { public static class ThreadPrinter implements Runnable { private String name; private Object prev; private Object self; private ThreadPrinter(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) {// 多線程並發,不能用if,必須使用whil循環 synchronized (prev) { // 先獲取 prev 鎖 synchronized (self) {// 再獲取 self 鎖 System.out.print(name);// 打印 count--; self.notifyAll();// 喚醒其他線程競爭self鎖,注意此時self鎖並未立即釋放。 } // 此時執行完self的同步塊,這時self鎖才釋放。 try { if (count == 0) {// 如果count==0,表示這是最后一次打印操作,通過notifyAll操作釋放對象鎖。 prev.notifyAll(); } else { prev.wait(); // 立即釋放 prev鎖,當前線程休眠,等待喚醒 } } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static void main(String[] args) throws Exception { Object a = new Object(); Object b = new Object(); Object c = new Object(); ThreadPrinter pa = new ThreadPrinter("A", c, a); ThreadPrinter pb = new ThreadPrinter("B", a, b); ThreadPrinter pc = new ThreadPrinter("C", b, c); new Thread(pa).start(); Thread.sleep(10);// 保證初始ABC的啟動順序 new Thread(pb).start(); Thread.sleep(10); new Thread(pc).start(); Thread.sleep(10); } }

2、使用Lock 和 state標志

public class Lock_State_ABC { private static Lock lock=new ReentrantLock(); private static int state=0;//通過state的值來確定是哪個線程打印 static class ThreadA extends Thread{ @Override public void run(){ for (int i = 0; i <10 ; ) { try{ lock.lock(); while(state%3==0){// 多線程並發,不能用if,必須用循環測試等待條件,避免虛假喚醒 System.out.print("A"); state++; i++; } }finally{ lock.unlock(); } } } } static class ThreadB extends Thread{ @Override public void run(){ for (int i = 0; i <10 ; ) { try{ lock.lock(); while(state%3==1){ System.out.print("B"); state++; i++; } }finally{ lock.unlock(); } } } } static class ThreadC extends Thread{ @Override public void run(){ for (int i = 0; i <10 ; ) { try{ lock.lock(); while(state%3==2){ System.out.print("C"); state++; i++; } }finally{ lock.unlock(); } } } } public static void main(String[] args) { new ThreadA().start(); new ThreadB().start(); new ThreadC().start(); } } //輸出ABCABCABCABCABCABCABCABCABCABC

3、使用Lock->ReentrantLock 和Condition(await 、signal、signalAll)

與ReentrantLock搭配的通行方式是Condition,如下:

private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); condition.await();//this.wait(); condition.signal();//this.notify(); condition.signalAll();//this.notifyAll();

Condition是被綁定到Lock上的,必須使用lock.newCondition()才能創建一個Condition。從上面的代碼可以看出,Synchronized能實現的通信方式,Condition都可以實現,功能類似的代碼寫在同一行中。這樣解題思路就和第一種方法基本一致,只是采用的方法不同。

public class Lock_Condition_ABC { private static Lock lock = new ReentrantLock(); private static Condition A = lock.newCondition(); private static Condition B = lock.newCondition(); private static Condition C = lock.newCondition(); private static int count = 0; static class ThreadA extends Thread { @Override public void run() { try { lock.lock(); for (int i = 0; i < 10; i++) { while (count % 3 != 0){//注意這里是不等於0,也就是說沒輪到該線程執行,之前一直等待狀態 A.await(); //該線程A將會釋放lock鎖,構造成節點加入等待隊列並進入等待狀態 } System.out.print("A"); count++; B.signal(); // A執行完喚醒B線程 } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } static class ThreadB extends Thread { @Override public void run() { try { lock.lock(); for (int i = 0; i < 10; i++) { while (count % 3 != 1) B.await();// B釋放lock鎖,當前面A線程執行后會通過B.signal()喚醒該線程 System.out.print("B"); count++; C.signal();// B執行完喚醒C線程 } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } static class ThreadC extends Thread { @Override public void run() { try { lock.lock(); for (int i = 0; i < 10; i++) { while (count % 3 != 2) C.await();// C釋放lock鎖 System.out.print("C"); count++; A.signal();// C執行完喚醒A線程 } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { new ThreadA().start(); new ThreadB().start(); new ThreadC().start(); } }

4、使用Semaphore

4.1 Semaphore介紹

Semaphore又稱信號量,是操作系統中的一個概念,在Java並發編程中,信號量控制的是線程並發的數量。
————————————————————————————————————————————————
Semaphore實現原理簡單理解:
Semaphore是用來保護一個或者多個共享資源的訪問,Semaphore信號量內部維護了一個計數器,其值為可以訪問的共享資源的個數。一個線程要訪問共享資源,先獲得信號量,如果信號量的計數器值大於1,意味着有共享資源可以訪問,則使其計數器值減去1,再訪問共享資源。

就好比一個廁所管理員,站在門口,只有廁所有空位,就開門允許與空側數量等量的人進入廁所。多個人進入廁所后,相當於N個人來分配使用N個空位。為避免多個人來同時競爭同一個側衛,在內部仍然使用鎖來控制資源的同步訪問。

如果計數器值為0,線程進入休眠。當某個線程使用完共享資源后,釋放信號量,並將信號量內部的計數器加1,之前進入休眠的線程將被喚醒並再次試圖獲得信號量。
————————————————————————————————————————————————
Semaphore內部主要通過AQS(AbstractQueuedSynchronizer)實現線程的管理。Semaphore有兩個構造函數,第一個參數permits表示許可數,它最后傳遞給了AQS的state值。線程在運行時首先獲取許可,如果成功,許可數就減1,線程運行,當線程運行結束就釋放許可,許可數就加1。如果許可數為0,則獲取失敗,線程位於AQS的等待隊列中,它會被其它釋放許可的線程喚醒。在創建Semaphore對象的時候還可以指定它的公平性。一般常用非公平的信號量,非公平信號量是指在獲取許可時先嘗試獲取許可,而不必關心是否已有需要獲取許可的線程位於等待隊列中,如果獲取失敗,才會入列。而公平的信號量在獲取許可時首先要查看等待隊列中是否已有線程,如果有則入列。

//非公平的構造函數 public Semaphore(int permits);//permits=10,表示允許10個線程獲取許可證,最大並發數是10; ////通過fair參數決定公平性 public Semaphore(int permits,boolean fair) Semaphore semaphore = new Semaphore(10,true); semaphore.acquire(); //線程獲取許可證 //do something here semaphore.release(); //線程歸還許可證

4.2 代碼

public class Semaphore_ABC { // 以A開始的信號量,初始信號量數量為1 private static Semaphore A = new Semaphore(1); // B、C信號量,A完成后開始,初始信號數量為0 private static Semaphore B = new Semaphore(0); private static Semaphore C = new Semaphore(0); static class ThreadA extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { A.acquire();// A獲取信號執行,A信號量減1,當A為0時將無法繼續獲得該信號量 System.out.print("A"); B.release();// B釋放信號,B信號量加1(初始為0),此時可以獲取B信號量 } } catch (InterruptedException e) { e.printStackTrace(); } } } static class ThreadB extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { B.acquire(); System.out.print("B"); C.release(); } } catch (InterruptedException e) { e.printStackTrace(); } } } static class ThreadC extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { C.acquire(); System.out.println("C"); A.release(); } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { new ThreadA().start(); new ThreadB().start(); new ThreadC().start(); } } 

可以看到信號量的變化情況如下:
初始(A=1,B=0,C=0)—>第一次執行線程A時(A=1,B=0,C=0)—->第一次執行線程B時(A=0,B=1,C=0)—->第一次執行線程C時(A=0,B=0,C=1)—>第二次執行線程A(A=1,B=0,C=0)如此循環。

5、使用AtomicInteger

public class Atomtic_ABC { private AtomicInteger ai = new AtomicInteger(0); private static final int MAX_SYC_VALUE = 3 * 10; private class RunnableA implements Runnable { public void run() { while (ai.get() < MAX_SYC_VALUE-1) { if (ai.get() % 3 == 0) { System.out.print("A"); ai.getAndIncrement(); } } } } private class RunnableB implements Runnable { public void run() { while (ai.get() < MAX_SYC_VALUE) { if (ai.get() % 3 == 1) { System.out.print("B"); ai.getAndIncrement(); } } } } private class RunnableC implements Runnable { public void run() { while (ai.get() < MAX_SYC_VALUE) { if (ai.get() % 3 == 2) { System.out.println("C"); ai.getAndIncrement(); } } } } public static void main(String[] args) { Atomtic_ABC atomic_ABC = new Atomtic_ABC(); ExecutorService service = Executors.newFixedThreadPool(3); service.execute(atomic_ABC.new RunnableA()); service.execute(atomic_ABC.new RunnableB()); service.execute(atomic_ABC.new RunnableC()); service.shutdown(); } } 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM