1. 程式人生 > >3個執行緒迴圈列印ABC

3個執行緒迴圈列印ABC

三個執行緒迴圈列印ABC多次,要求同時開啟多個執行緒

核心思路:三個執行緒分別列印A,B,C;一個執行完後喚醒另外一個執行緒。主要使用Object.wait()Object.notify()這兩個方法。

實現方法

  1. A->B, B->C,C->A之間狀態的轉換分別使用不同的物件鎖
  2. 不同狀態之間的轉換使用同一個狀態鎖,完成後使用notifyAll,並在自己的執行緒中檢查,是否滿足執行條件,不滿足則繼續wait。

程式碼實現

  1. 使用不同的物件鎖

這種方法其實是有問題的,由於巢狀的同步物件,存在死鎖的風險,在兩個的時候就比較容易提現出來。3個執行緒就不太容易復現,因為要3個執行緒同時進入Thread.yeild()

那段程式碼,然後同時喚醒,都想要獲取next的監視器鎖,但是都獲取不到。

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;
        }

    }
}
  1. 使用同一個監視器鎖,

一個執行緒結束後喚醒所有的執行緒,讓其自己判斷是否要往下執行。封裝了鎖持有器,所有的執行緒都對鎖同步,在這個物件中顯示出接下來該執行哪個。只有一個鎖,不會有死鎖的情況,但是每次都是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;
        }

    }

}

其他

  1. 在使用wait和notify這兩個方法必須在該物件的同步區內。否則會丟擲異常IllegalMonitorStateException這兩個是native的方法,看不到細節
  2. 不要使用巢狀的物件鎖,可能會出現死鎖。
  3. 內部類的物件可以使用外部類的屬性(Outter.this.xxx)
  4. 監視器鎖不能被賦值,一旦改了就不是同一個監視器鎖了
  5. 採用自旋鎖避免執行緒的異常喚醒(檢查下是否滿足往下執行的條件)