3個執行緒迴圈列印ABC
阿新 • • 發佈:2019-01-10
三個執行緒迴圈列印ABC多次,要求同時開啟多個執行緒
核心思路:三個執行緒分別列印A,B,C;一個執行完後喚醒另外一個執行緒。主要使用Object.wait()
和Object.notify()
這兩個方法。
實現方法
- A->B, B->C,C->A之間狀態的轉換分別使用不同的物件鎖
- 不同狀態之間的轉換使用同一個狀態鎖,完成後使用notifyAll,並在自己的執行緒中檢查,是否滿足執行條件,不滿足則繼續wait。
程式碼實現
- 使用不同的物件鎖
這種方法其實是有問題的,由於巢狀的同步物件,存在死鎖的風險,在兩個的時候就比較容易提現出來。3個執行緒就不太容易復現,因為要3個執行緒同時進入Thread.yeild()
public class CrycleThreader { private void test() { LockHolder A = new LockHolder(); LockHolder B = new LockHolder(); LockHolder C = new LockHolder(); A.setReady(true); CycleClass cycleA = new CycleClass(A, B, "A", 10); CycleClass cycleB = new CycleClass(B, A, "B", 10); // CycleClass cycleC = new CycleClass(C, A, "C", 10); cycleA.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } cycleB.start(); // cycleC.start(); } public static void main(String[] args) { new CrycleThreader().test(); // new CrycleThreader().testFail(); } // private class CycleClass extends Thread { private volatile LockHolder done; private volatile LockHolder next; private String name; private int count; /** * */ public CycleClass(LockHolder done, LockHolder next, String name, int count) { this.done = done; this.next = next; this.name = name; this.count = count; } @Override public void run() { while (count-- > 0) { synchronized (done) { while (!done.isReady()) { try { done.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } done.setReady(false); next.setReady(true); System.out.println(name + "--> " + count);//如果放到next.notify()後面 會不正常的 //Thread.yield(); synchronized (next) { next.notify(); } } } } } private class LockHolder { private boolean ready = false; /** * Getter method for property <tt>ready</tt>. * * @return property value of ready */ public boolean isReady() { return ready; } /** * Setter method for property <tt>ready</tt>. * * @param ready * value to be assigned to property ready */ public void setReady(boolean ready) { this.ready = ready; } } }
- 使用同一個監視器鎖,
一個執行緒結束後喚醒所有的執行緒,讓其自己判斷是否要往下執行。封裝了鎖持有器,所有的執行緒都對鎖同步,在這個物件中顯示出接下來該執行哪個。只有一個鎖,不會有死鎖的情況,但是每次都是notifyAll,在多執行緒的情況下可能會造成執行緒切換上下文換入換出。 但是在我的機子上3個執行緒2比1塊
public class CycleHandler { // time cost 3722 public void doTest() { String A = "A"; String B = "B"; String C = "C"; int count = 100000; LockHolder holder = new LockHolder(); holder.setReadyObj(A); CountDownLatch latch = new CountDownLatch(1); Cycle cycleA = new Cycle(A, B, holder, count, latch); Cycle cycleB = new Cycle(B, C, holder, count, latch); Cycle cycleC = new Cycle(C, A, holder, count, latch); long startTime = System.currentTimeMillis(); cycleA.start(); cycleB.start(); cycleC.start(); latch.countDown(); try { cycleA.join(); cycleB.join(); cycleC.join(); } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println("execute time: " + (endTime - startTime)); } public static void main(String[] args) { new CycleHandler().doTest(); } private class Cycle extends Thread { private final LockHolder lockHoder; private Object next; private Object cur; private int count; private CountDownLatch latch; public Cycle(Object cur, Object next, LockHolder holder, int count, CountDownLatch lacth) { this.cur = cur; this.next = next; this.lockHoder = holder; this.count = count; this.latch = lacth; } @Override public void run() { try { latch.await(); } catch (InterruptedException e1) { // logger.error("", e); e1.printStackTrace(); } while (count-- > 0) { synchronized (lockHoder) { while (!lockHoder.getReadyObj().equals(cur)) { try { lockHoder.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(cur.toString() + "--> " + count); lockHoder.setReadyObj(next); lockHoder.notifyAll(); } } } } private class LockHolder { private Object readyObj; public Object getReadyObj() { return readyObj; } public void setReadyObj(Object readyObj) { this.readyObj = readyObj; } } }
其他
- 在使用wait和notify這兩個方法必須在該物件的同步區內。否則會丟擲異常
IllegalMonitorStateException
這兩個是native的方法,看不到細節 - 不要使用巢狀的物件鎖,可能會出現死鎖。
- 內部類的物件可以使用外部類的屬性(Outter.this.xxx)
- 監視器鎖不能被賦值,一旦改了就不是同一個監視器鎖了
- 採用自旋鎖避免執行緒的異常喚醒(檢查下是否滿足往下執行的條件)