Java并發(fā)編程(一)如何保證線程順序執(zhí)行

只要了解過多線程,我們就知道線程開始的順序跟執(zhí)行的順序是不一樣的汉柒。如果只是創(chuàng)建三個線程然后執(zhí)行误褪,最后的執(zhí)行順序是不可預期的。這是因為在創(chuàng)建完線程之后碾褂,線程執(zhí)行的開始時間取決于CPU何時分配時間片兽间,線程可以看成是相對于的主線程的一個異步操作。

public class FIFOThreadExample {
    public synchronized static void foo(String name) {
        System.out.print(name);
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

輸出結(jié)果:ACB/ABC/CBA...

那么我們該如何保證線程的順序執(zhí)行呢正塌?

如何保證線程的順序執(zhí)行?

1. 使用Thread.join()實現(xiàn)

Thread.join()的作用是讓父線程等待子線程結(jié)束之后才能繼續(xù)運行嘀略。以上述例子為例,main()方法所在的線程是父線程,在其中我們創(chuàng)建了3個子線程A,B,C,子線程的執(zhí)行相對父線程是異步的垃帅,不能保證順序性弛针。而對子線程使用Thread.join()方法之后就可以讓父線程等待子線程運行結(jié)束后,再開始執(zhí)行父線程村斟,這樣子線程執(zhí)行被強行變成了同步的,我們用Thread.join()方法就能保證線程執(zhí)行的順序性。

public class FIFOThreadExample {
    
    public static void foo(String name) {
        System.out.print(name);
    }

    public static void main(String[] args) throws InterruptedException{
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
    }
}

輸出結(jié)果:ABC

2. 使用單線程線程池來實現(xiàn)

另一種保證線程順序執(zhí)行的方法是使用一個單線程的線程池窥淆,這種線程池中只有一個線程,相應的巍杈,內(nèi)部的線程會按加入的順序來執(zhí)行忧饭。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FIFOThreadExample {

    public static void foo(String name) {
        System.out.print(name);
    }

    public static void main(String[] args) throws InterruptedException{
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(thread1);
        executor.submit(thread2);
        executor.submit(thread3);
        executor.shutdown();
    }
}

輸出結(jié)果:ABC

3. 使用volatile關鍵字修飾的信號量實現(xiàn)

上面兩種的思路都是讓保證線程的執(zhí)行順序,讓線程按一定的順序執(zhí)行筷畦。這里介紹第三種思路词裤,那就是線程可以無序運行,但是執(zhí)行結(jié)果按順序執(zhí)行鳖宾。
你應該可以想到吼砂,三個線程都被創(chuàng)建并start(),這時候三個線程隨時都可能執(zhí)行run()方法鼎文。因此為了保證run()執(zhí)行的順序性渔肩,我們肯定需要一個信號量來讓線程知道在任意時刻能不能執(zhí)行邏輯代碼。
另外拇惋,因為三個線程是獨立的周偎,這個信號量的變化肯定需要對其他線程透明抹剩,因此volatile關鍵字也是必須要的。

public class TicketExample2 {

    //信號量
    static volatile int ticket = 1;
    //線程休眠時間
    public final static int SLEEP_TIME = 1;

    public static void foo(int name){
        //因為線程的執(zhí)行順序是不可預期的蓉坎,因此需要每個線程自旋
        while (true) {
            if (ticket == name) {
                try {
                    Thread.sleep(SLEEP_TIME);
                    //每個線程循環(huán)打印3次
                    for (int i = 0; i < 3; i++) {
                        System.out.println(name + " " + i);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //信號量變更
                ticket = name%3+1;
                return;

            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> foo(1));
        Thread thread2 = new Thread(() -> foo(2));
        Thread thread3 = new Thread(() -> foo(3));
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

執(zhí)行結(jié)果:
1 0
1 1
1 2
2 0
2 1
2 2
3 0
3 1
3 2

4. 使用Lock和信號量實現(xiàn)

此種方法的思想跟第三種方法是一樣的澳眷,都是不考慮線程執(zhí)行的順序而是考慮用一些方法控制線程執(zhí)行業(yè)務邏輯的順序。這里我們同樣用一個原子類型信號量ticket蛉艾,當然你可以不用原子類型钳踊,這里我只是為了保證自增操作的線程安全。然后我們用了一個可重入鎖ReentrantLock伺通。用來給方法加鎖箍土,當一個線程拿到鎖并且標識位正確的時候開始執(zhí)行業(yè)務邏輯,執(zhí)行完畢后喚醒下一個線程罐监。
這里我們不需要使用while進行自旋操作了吴藻,因為Lock可以讓我們喚醒指定的線程,所以改成if就可以實現(xiàn)順序的執(zhí)行弓柱。

public class TicketExample3 {
    //信號量
    AtomicInteger ticket = new AtomicInteger(1);
    public Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private Condition[] conditions = {condition1, condition2, condition3};

    public void foo(int name) {
        try {
            lock.lock();
            //因為線程的執(zhí)行順序是不可預期的沟堡,因此需要每個線程自旋
            System.out.println("線程" + name + " 開始執(zhí)行");
            if(ticket.get() != name) {
                try {
                    System.out.println("當前標識位為" + ticket.get() + ",線程" + name + " 開始等待");
                    //開始等待被喚醒
                    conditions[name - 1].await();
                    System.out.println("線程" + name + " 被喚醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(name);
            ticket.getAndIncrement();
            if (ticket.get() > 3) {
                ticket.set(1);
            }
            //執(zhí)行完畢,喚醒下一次矢空。1喚醒2,2喚醒3
            conditions[name % 3].signal();
        } finally {
            //一定要釋放鎖
            lock.unlock();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        TicketExample3 example = new TicketExample3();
        Thread t1 = new Thread(() -> {
            example.foo(1);
        });
        Thread t2 = new Thread(() -> {
            example.foo(2);
        });
        Thread t3 = new Thread(() -> {
            example.foo(3);
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

輸出結(jié)果:
線程2 開始執(zhí)行
當前標識位為1,線程2 開始等待
線程1 開始執(zhí)行
1
線程3 開始執(zhí)行
當前標識位為2,線程3 開始等待
線程2 被喚醒
2
線程3 被喚醒
3

上述的執(zhí)行結(jié)果并非唯一航罗,但可以保證打印的順序一定是123這樣的順序。

參考文章

java 多線程 實現(xiàn)多個線程的順序執(zhí)行 - Hoonick - 博客園 (cnblogs.com)
Java lock鎖的一些細節(jié)_筆記小屋-CSDN博客
VolatileCallSite (Java Platform SE 8 ) (oracle.com)
java保證多線程的執(zhí)行順序 - james.yj - 博客園 (cnblogs.com)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屁药,一起剝皮案震驚了整個濱河市粥血,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酿箭,老刑警劉巖复亏,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缭嫡,居然都是意外死亡缔御,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門妇蛀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耕突,“玉大人,你說我怎么就攤上這事评架【熳拢” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵纵诞,是天一觀的道長上祈。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么雇逞? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮茁裙,結(jié)果婚禮上塘砸,老公的妹妹穿的比我還像新娘。我一直安慰自己晤锥,他們只是感情好掉蔬,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著矾瘾,像睡著了一般女轿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壕翩,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天蛉迹,我揣著相機與錄音,去河邊找鬼放妈。 笑死北救,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的芜抒。 我是一名探鬼主播珍策,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宅倒!你這毒婦竟也來了攘宙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拐迁,失蹤者是張志新(化名)和其女友劉穎蹭劈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唠亚,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡链方,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灶搜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祟蚀。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖割卖,靈堂內(nèi)的尸體忽然破棺而出前酿,到底是詐尸還是另有隱情,我是刑警寧澤鹏溯,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布罢维,位于F島的核電站,受9級特大地震影響丙挽,放射性物質(zhì)發(fā)生泄漏肺孵。R本人自食惡果不足惜匀借,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望平窘。 院中可真熱鬧吓肋,春花似錦、人聲如沸瑰艘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽紫新。三九已至均蜜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芒率,已是汗流浹背囤耳。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敲董,地道東北人紫皇。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像腋寨,于是被迫代替她去往敵國和親聪铺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容