八種控制線程順序的方法


 

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

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

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

作者原創文章,謝絕一切轉載!

本文只發表在"公眾號"和"博客園",其他均屬復制粘貼!如果覺得排版不清晰,請查看公眾號文章。 

准備:

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(回環柵欄),我想了下,這個對象不適合控制順序,只適合線程相互等待,然后一起運行,比如我們約好今天一起去吃大餐,集合后,至於誰先邁出出發的第一步,這個沒法控制,故舍棄不用,

全文完!

 


我的其他文章:

只寫原創,敬請關注


免責聲明!

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



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