1. 程式人生 > >八種控制執行緒順序的方法

八種控制執行緒順序的方法

 

各位看官,我去年路過陳家村時,聽到大神們在討論一些排序演算法,比如猴子排序法、睡眠排序法等,猴子排序法就是給猴子一堆亂序的數,

讓它自己玩,最後總有一個順序是對的!睡眠排序法,按數的大小分配執行緒睡眠時間,數越大睡眠時間就越長,然後同時啟動全部執行緒,按

先後輸出排序即成!想想也不無道理,那我就展開說說睡眠排序法,如何玩轉執行緒執行順序控制。

準備:

Idea2019.03/Gradle6.0.1/JDK11.0.4

難度: 新手--戰士--老兵--大師

目標:

  1. 實現八種控制執行緒順序的方法

步驟:

為了遇見各種問題,同時保持時效性,我儘量使用最新的軟體版本。程式碼地址:本次無

 

第一招:執行緒配合join

publicclass ThreadJoinDemo {
    public static void main(String[] args) {
        final Thread thread1 = new Thread(
                ()->{
                    System.out.println("先買菜");
                }
        );
        final Thread thread2 = new Thread(
                ()->{
                    try {
                        thread1.join(); //Waits for this thread to die.
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("再煎蛋");
                }
        );
        final Thread thread3 = new Thread(
                ()->{
                    try {
                        thread2.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("後吃飯");
                }
        );

        thread3.start();
        thread1.start();
        thread2.start();
    }
}

解析:呼叫thread.join()方法,當前執行緒將等待被join執行緒執行結束

 

第二招:主執行緒配合join

publicclass ThreadJoinDemo2 {
    public static void main(String[] args) throws InterruptedException {
        final Thread thread1 = new Thread(
                ()->{
                    System.out.println("先買菜");
                }
        );
        final Thread thread2 = new Thread(
                ()->{
                    System.out.println("再煎蛋");
                }
        );
        final Thread thread3 = new Thread(
                ()->{
                    System.out.println("後吃飯");
                }
        );

        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
    }
}

解析:同上

 

第三招:synchronized鎖,配合鎖的wait/siganl喚醒機制

publicclass ThreadJoinDemo3 {
    privatestaticfinalbyte[] myLock1 = newbyte[0];
    privatestaticfinalbyte[] myLock2 = newbyte[0];
    privatestatic Boolean t1Run = false;
    privatestatic Boolean t2Run = false;

    public static void main(String[] args) {
        final Thread thread1 = new Thread(
                ()->{
                    synchronized (myLock1){
                        System.out.println("先買車");
                        t1Run = true;
                        myLock1.notifyAll();
                    }
                }
        );
        final Thread thread2 = new Thread(
                ()->{
                    synchronized (myLock1){
                        try {
                            if (!t1Run){
                                System.out.println("買菜路上。。。");
                                myLock1.wait();
                            }
                            synchronized (myLock2){
                                t2Run = true;
                                System.out.println("後吃飯");
                                myLock2.notifyAll();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
                }
        );
        final Thread thread3 = new Thread(
                ()->{
                    synchronized (myLock2){
                        try {
                            if (!t2Run){
                                System.out.println("煎蛋糊了。。。");
                                myLock2.wait();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("後吃飯");
                        myLock2.notifyAll();
                    }
                }
        );

        thread3.start();
        thread2.start();
        thread1.start();
    }
}

解析:執行執行緒前,先去嘗試獲取鎖,如果獲取失敗,就進入等待狀態, 

注意 1.加了兩個狀態變數的作用:如果thread1先執行完了,thread2才執行,thread2在等待thread1喚醒,這將導致thread2永遠等待,

因為wait將使得當前執行緒進入等待直到被喚醒,2.使用空的byte[]陣列做鎖物件,為啥?因為體積小,效率高啊!

 

第四招:newSingleThreadExecutor執行緒池

publicclass ThreadJoinDemo4 {
    public static void main(String[] args) {
        final Thread thread1 = new Thread(
                ()->{
                    System.out.println("先買菜");
                }
        );
        final Thread thread2 = new Thread(
                ()->{
                    System.out.println("再煎蛋");
                }
        );
        final Thread thread3 = new Thread(
                ()->{
                    System.out.println("後吃飯");
                }
        );

        ExecutorService threadPool = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
        threadPool.submit(thread1);
        threadPool.submit(thread2);
        threadPool.submit(thread3);
        // 關閉執行緒池:生產環境請註釋掉,請君思考為啥?
        threadPool.shutdown();
    }
}

解析:newSingleThreadExecutor執行緒池物件中只有一個執行緒來執行任務,就會按照接收的任務順序執行,只需按序提交任務即可。

 

第五招:lock配合condition

publicclass ThreadJoinDemo5 {
    privatestaticfinal Lock lock = new ReentrantLock();
    privatestaticfinal Condition condition1 = lock.newCondition();
    privatestaticfinal Condition condition2 = lock.newCondition();
    privatestatic Boolean t1Run = false;
    privatestatic Boolean t2Run = false;

    public static void main(String[] args) {
        final Thread thread1 = new Thread(
                ()->{
                    // 注意lock/tryLock的區別: lock是void,沒獲取到鎖,則進入休眠,tryLock是返回Boolean,執行後立即返回true/false
                    lock.lock();
                    System.out.println("先買菜");
                    condition1.signal();
                    t1Run = true;
                    // 生產環境下這裡最好使用try/finally確保unlock執行
                    lock.unlock();
                }
        );
        final Thread thread2 = new Thread(
                ()->{
                    lock.lock();
                    try {
                        if (!t1Run){
                            // Causes the current thread to wait until it is signalled
                            condition1.await();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("再煎蛋");
                    t2Run = true;
                    condition2.signal();
                    lock.unlock();
                }
        );
        final Thread thread3 = new Thread(
                ()->{
                    lock.lock();
                    try {
                        if (!t2Run){
                            condition2.await();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("後吃飯");
                    lock.unlock();
                }
        );
        thread3.start();
        thread2.start();
        thread1.start();
    }

}

解析:lock物件上建立兩個condition,執行緒執行前先加鎖,若不是預期順序的執行緒啟動,則在該condition上進行wait等待直到收到signal訊號, 

注意點:condition 和 lock 執行先後關係,Before waiting on the condition the lock must be held by the current thread. await() will atomically

release the lock before waiting and re-acquire the lock before the wait returns.

 

第六招:CountDownLatch

publicclass ThreadJoinDemo6 {

    privatestatic  CountDownLatch countDownLatch1 = new CountDownLatch(1);
    privatestatic  CountDownLatch countDownLatch2 = new CountDownLatch(1);

    public static void main(String[] args) {
        final Thread thread1 = new Thread(
                ()->{
                    countDownLatch1.countDown();
                    System.out.println("先買菜");
                }
        );
        final Thread thread2 = new Thread(
                ()->{
                    try {
                        // 注意這裡不要寫成 Object.wait()
                        countDownLatch1.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("再煎蛋");
                    countDownLatch2.countDown();
                }
        );
        final Thread thread3 = new Thread(
                ()->{
                    try {
                        countDownLatch2.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("後吃飯");
                }
        );

        thread3.start();
        thread1.start();
        thread2.start();
    }
}

解析:CountDownLatch即“倒計數”,只有計數變為零時,參與者才能執行,我們設定兩個倒計數器,都置為1,非預期順序的執行緒,必須等待計數歸零。

 

第七招:Semaphore訊號量法

publicclass ThreadJoinDemo7 {

    privatestatic Semaphore semaphore1 = new Semaphore(0);
    privatestatic Semaphore semaphore2 = new Semaphore(0);

    public static void main(String[] args) {
        final Thread thread1 = new Thread(
                ()->{
                    semaphore1.release();
                    System.out.println("先買菜");
                }
        );
        final Thread thread2 = new Thread(
                ()->{
                    try {
                        semaphore1.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("再煎蛋");
                    semaphore2.release();
                }
        );
        final Thread thread3 = new Thread(
                ()->{
                    try {
                        semaphore2.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("後吃飯");
                }
        );

        thread1.start();
        thread3.start();
        thread2.start();
    }
}

解析:Semaphore訊號量物件,可以存放N個訊號量,可以原子性的釋放和請求這N個訊號量,我們先預設兩個存放0個訊號量的物件,

非預期順序的執行緒啟動後,無法獲取到訊號量,進入等待,直到前序執行緒釋放訊號量。

注意:Semaphore中可以為負值,這時候,就必須確保release發生在acquire前面,比如Semaphore(0)和Semaphore(-1)的情況:

Semaphore(-1)的release可以,require則進入休眠,Semaphore(0)的release可以,require則進入休眠,即只有permit大於0時,才能require成功!

 

第八招:終極大法,睡眠法!

略,留個家庭作業!


 

後記:

  1. lambda表示式,如果看官還覺得我這個執行緒建立的寫法不太爽,那就落伍啦,別再用下面的寫法了,如果使用Idea,則會自動提示轉為lambda表示式:
  2. Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("寫成這樣,表示您落伍了!");
                }
            });
  3. CyclicBarrier(迴環柵欄),我想了下,這個物件不適合控制順序,只適合執行緒相互等待,然後一起執行,比如我們約好今天一起去吃大餐,集合後,至於誰先邁出出發的第一步,這個沒法控制,故舍棄不用,

全文完!

 


我的其他文章:

  • 1 移動應用APP購物車(店鋪系列二)
  • 2 H5開發移動應用APP(店鋪系列一)
  • 3 阿里雲平臺OSS物件儲存
  • 4 Dubbo學習系列之十七(微服務Soul閘道器)
  • 5 Docker部署RocketMQ

只寫原創,敬請關注