1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Java中如何保證線程順序執(zhí)行

        共 12667字,需瀏覽 26分鐘

         ·

        2021-08-02 11:52

        關(guān)注我們,設(shè)為星標(biāo),每天7:30不見(jiàn)不散,架構(gòu)路上與您共享 

        回復(fù)"架構(gòu)師"獲取資源

          作者 |  六層樓

        來(lái)源 |  urlify.cn/IVz6vm

        只要了解過(guò)多線程,我們就知道線程開(kāi)始的順序跟執(zhí)行的順序是不一樣的。如果只是創(chuàng)建三個(gè)線程然后執(zhí)行,最后的執(zhí)行順序是不可預(yù)期的。這是因?yàn)樵趧?chuàng)建完線程之后,線程執(zhí)行的開(kāi)始時(shí)間取決于CPU何時(shí)分配時(shí)間片,線程可以看成是相對(duì)于的主線程的一個(gè)異步操作。

        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...

        那么我們?cè)撊绾伪WC線程的順序執(zhí)行呢?

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

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

        Thread.join()的作用是讓父線程等待子線程結(jié)束之后才能繼續(xù)運(yùn)行。以上述例子為例,main()方法所在的線程是父線程,在其中我們創(chuàng)建了3個(gè)子線程A,B,C,子線程的執(zhí)行相對(duì)父線程是異步的,不能保證順序性。而對(duì)子線程使用Thread.join()方法之后就可以讓父線程等待子線程運(yùn)行結(jié)束后,再開(kāi)始執(zhí)行父線程,這樣子線程執(zhí)行被強(qiáng)行變成了同步的,我們用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. 使用單線程線程池來(lái)實(shí)現(xiàn)

        另一種保證線程順序執(zhí)行的方法是使用一個(gè)單線程的線程池,這種線程池中只有一個(gè)線程,相應(yīng)的,內(nèi)部的線程會(huì)按加入的順序來(lá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關(guān)鍵字修飾的信號(hào)量實(shí)現(xiàn)

        上面兩種的思路都是讓保證線程的執(zhí)行順序,讓線程按一定的順序執(zhí)行。這里介紹第三種思路,那就是線程可以無(wú)序運(yùn)行,但是執(zhí)行結(jié)果按順序執(zhí)行。
        你應(yīng)該可以想到,三個(gè)線程都被創(chuàng)建并start(),這時(shí)候三個(gè)線程隨時(shí)都可能執(zhí)行run()方法。因此為了保證run()執(zhí)行的順序性,我們肯定需要一個(gè)信號(hào)量來(lái)讓線程知道在任意時(shí)刻能不能執(zhí)行邏輯代碼。
        另外,因?yàn)槿齻€(gè)線程是獨(dú)立的,這個(gè)信號(hào)量的變化肯定需要對(duì)其他線程透明,因此volatile關(guān)鍵字也是必須要的。

        public class TicketExample2 {

            //信號(hào)量
            static volatile int ticket = 1;
            //線程休眠時(shí)間
            public final static int SLEEP_TIME = 1;

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

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //信號(hào)量變更
                        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和信號(hào)量實(shí)現(xiàn)

        此種方法的思想跟第三種方法是一樣的,都是不考慮線程執(zhí)行的順序而是考慮用一些方法控制線程執(zhí)行業(yè)務(wù)邏輯的順序。這里我們同樣用一個(gè)原子類(lèi)型信號(hào)量ticket,當(dāng)然你可以不用原子類(lèi)型,這里我只是為了保證自增操作的線程安全。然后我們用了一個(gè)可重入鎖ReentrantLock。用來(lái)給方法加鎖,當(dāng)一個(gè)線程拿到鎖并且標(biāo)識(shí)位正確的時(shí)候開(kāi)始執(zhí)行業(yè)務(wù)邏輯,執(zhí)行完畢后喚醒下一個(gè)線程。
        這里我們不需要使用while進(jìn)行自旋操作了,因?yàn)長(zhǎng)ock可以讓我們喚醒指定的線程,所以改成if就可以實(shí)現(xiàn)順序的執(zhí)行。

        public class TicketExample3 {
            //信號(hào)量
            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();
                    //因?yàn)榫€程的執(zhí)行順序是不可預(yù)期的,因此需要每個(gè)線程自旋
                    System.out.println("線程" + name + " 開(kāi)始執(zhí)行");
                    if(ticket.get() != name) {
                        try {
                            System.out.println("當(dāng)前標(biāo)識(shí)位為" + ticket.get() + ",線程" + name + " 開(kāi)始等待");
                            //開(kāi)始等待被喚醒
                            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 開(kāi)始執(zhí)行
        當(dāng)前標(biāo)識(shí)位為1,線程2 開(kāi)始等待
        線程1 開(kāi)始執(zhí)行
        1
        線程3 開(kāi)始執(zhí)行
        當(dāng)前標(biāo)識(shí)位為2,線程3 開(kāi)始等待
        線程2 被喚醒
        2
        線程3 被喚醒
        3

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




        到此文章就結(jié)束了。如果今天的文章對(duì)你在進(jìn)階架構(gòu)師的路上有新的啟發(fā)和進(jìn)步,歡迎轉(zhuǎn)發(fā)給更多人。歡迎加入架構(gòu)師社區(qū)技術(shù)交流群,眾多大咖帶你進(jìn)階架構(gòu)師,在后臺(tái)回復(fù)“加群”即可入群。







        這些年小編給你分享過(guò)的干貨

        1.第七期打卡送書(shū)5本(5月1日-6月1日)

        2.ERP系統(tǒng),自帶進(jìn)銷(xiāo)存+財(cái)務(wù)+生產(chǎn)功能,拿來(lái)即用

        3.帶工作流的SpringBoot后臺(tái)管理項(xiàng)目快速開(kāi)發(fā)解決方案
        4.最好的OA系統(tǒng),拿來(lái)即用,非常方便

        5.SpringBoot+Vue完整的外賣(mài)系統(tǒng),手機(jī)端和后臺(tái)管理,附源碼!

        轉(zhuǎn)發(fā)在看就是最大的支持??

        瀏覽 45
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            老公亲秘密花园的时候该如何回应 | 999久久久又粗又大国产精品 | 操逼片视频 | 欧美精品一区二区三区日韩免费 | 女人与公拘交酡全过程网站 | 2024AV在线 | 色哟哟无码 | 99re在线视频观看精品观看 | 吃奶插逼 | 夜夜嗨国产 |