讓線程乖乖~按順序執行

最近總是遇到這樣的問題:怎麼控制線程訪問順序?怎麼讓線程B在線程A執行之後再執行?怎麼讓線程A、B、C按順序打印ABCABC?     以上都可以歸結爲線程的順序執行問題,有這麼4種方案:

(1)join():“等你執行結束,我再執行”

(2)singleThreadPool:只有一個線程的線程池,任務乖乖在隊列中等待被執行

(3)wait/notify機制:“兄弟們,醒醒,到你了”

(4)ReentrantLock 的 condition 的 await/signal機制:“那個兄弟,醒醒,到你了”

接下來就這四種方式進行詳細說明

 

一、Join()

1、簡介:join(),等待線程結束,才能繼執行。

2、本質:讓線程wait在當前線程對象實例上,觀察join的源碼:

    public final void join() throws InterruptedException {
        join(0);
    }
public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        //---------重點-------------
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        //--------------------------
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

解讀:阻塞當前線程,直到目標線程運行結束,如果參數是0,意味着無限等待。

3、join實現線程順序執行

(1)方式1:在run()中調用join方法。

public class M1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> System.out.println("A"));
        Thread t2 = new Thread(() -> {
            try {
                t1.join();  //瞧,線程2等待線程1執行結束,然後再執行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");});
        Thread t3 = new Thread(() -> {
            try {
                t2.join();  //線程3等待線程2執行結束,然後再執行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("C");});
        t1.start();
        t3.start();
        t2.start();
        //輸出:A B C
    }
}

(2)方式2:在主方法中調用join

public class M2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> System.out.println("A"));
        Thread t2 = new Thread(() -> System.out.println("B"));
        Thread t3 = new Thread(() -> System.out.println("C"));
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        t3.start();
        //輸出:A  B  C
    }
}

 

二、單線程線程池

1、簡介:只有一個線程的線程池,任務都在等待隊列中排隊,所以它們是一個一個執行的

2、兩種創建方式:

ExecutorService single=new ThreadPoolExecutor(1,1,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
ExecutorService single = Executors.newSingleThreadExecutor();

3、單線程線程池實現順序執行

public class M2 {
    public static void main(String[] args){
        Thread t5=new Thread(()->System.out.println("A"));
        Thread t6=new Thread(()->System.out.println("B"));
        Thread t7=new Thread(()->System.out.println("C"));
        ExecutorService single=new ThreadPoolExecutor(1,1,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
        single.submit(t5);
        single.submit(t7);
        single.submit(t6);
    }
}

 

三、wait/notify機制

1、簡介:有多種實現方式,這裏介紹通過一個變量的取值來控制線程執行順序。

      注意:wait/notify 要求獲得對象的monitor,所以只能在同步方法或同步代碼塊中使用

2、wait/notify實現線程順序執行

  • 先創建一個類,類中包含一個變量,flag。我們約定,flag=0時,輸出A,flag=1時,輸出B,flag=2,輸出C
class MyThread {
    private static int flag=0;
    //str:稍後要打印的值;order:取值範圍[0,1,2],分別代表先後順序
    public synchronized void printStr(String str,int order){
        while (flag!=order){
            try{
                this.wait(); //否則釋放鎖
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(str);
        if (order<2)
            flag++;
        else
            flag=1;
        this.notifyAll(); //喚起其他線程
    }
}
  • 主方法中調用
public class M3 {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread t8 = new Thread(new Runnable() {
            @Override
            public void run() {
                myThread.printStr("A", 0); //第一個,打印A
            }
        });
        Thread t9 = new Thread(new Runnable() {
            @Override
            public void run() {
                myThread.printStr("B", 2); //第3,打印B
            }
        });
        Thread t10 = new Thread(new Runnable() {
            @Override
            public void run() {
                myThread.printStr("C", 1); //第二,打印C
            }
        });
        t8.start();
        t9.start();
        t10.start();
        //執行結果:A    C     B
    }
}

 

 

四、condition的await/signal機制

1、簡介:這個機制和wait/notify很像,(筆者狹隘地認爲,condition一定情況下可以更加精確~)

2、Conditon:await/signal要配合ReentrantLock使用,通過ReentrantLock的newConditon方法生成一個與當前重入鎖綁定的Condition實例,讓我們能在線程何時的時間等待,或在一個特定的時刻獲得通知。阻塞隊列之所以能夠阻塞,底層也是利用了condition哦

3、await/signal實現線程順序執行

public class M5{
    private static Lock lock=new ReentrantLock();
    private static Condition condition1=lock.newCondition();
    private static Condition condition2=lock.newCondition();
    private static boolean printA=true;  //先打印A
    private static boolean printB=false; //先打印B
    public static void main(String[] args) throws InterruptedException {
        Thread t11=new Thread(() -> {
            try{
                lock.lock();
                if (!printA) {
                   condition1.await(); //如果當前printA=false,則在條件1上等待
                }
                System.out.println("A");
                printB=true;
                printA=false;
                condition2.signal(); //喚醒在condition2上等待的線程
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        });
        Thread t12=new Thread(() -> {
            try{
                lock.lock();
                if (!printB){
                    condition2.await(); 
                }
                System.out.println("B");
                printA=true;
                printB=false;
                condition1.signal();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        });
        t11.start();
        t12.start();
    }

}

 

參考資料:

1、《Java高併發程序設計》

2、讓線程按順序執行8種方法:https://www.cnblogs.com/wenjunwei/p/10573289.html

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章