您好,登錄后才能下訂單哦!
小編給大家分享一下Java中怎么保證線程順序執(zhí)行,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
只要了解過多線程,我們就知道線程開始的順序跟執(zhí)行的順序是不一樣的。如果只是創(chuàng)建三個(gè)線程然后執(zhí)行,最后的執(zhí)行順序是不可預(yù)期的。這是因?yàn)樵趧?chuàng)建完線程之后,線程執(zhí)行的開始時(shí)間取決于CPU何時(shí)分配時(shí)間片,線程可以看成是相對于的主線程的一個(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...
那么我們該如何保證線程的順序執(zhí)行呢?
Thread.join()
的作用是讓父線程等待子線程結(jié)束之后才能繼續(xù)運(yùn)行。以上述例子為例,main()
方法所在的線程是父線程,在其中我們創(chuàng)建了3個(gè)子線程A,B,C,子線程的執(zhí)行相對父線程是異步的,不能保證順序性。而對子線程使用Thread.join()
方法之后就可以讓父線程等待子線程運(yùn)行結(jié)束后,再開始執(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
另一種保證線程順序執(zhí)行的方法是使用一個(gè)單線程的線程池,這種線程池中只有一個(gè)線程,相應(yīng)的,內(nèi)部的線程會(huì)按加入的順序來執(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
上面兩種的思路都是讓保證線程的執(zhí)行順序,讓線程按一定的順序執(zhí)行。這里介紹第三種思路,那就是線程可以無序運(yùn)行,但是執(zhí)行結(jié)果按順序執(zhí)行。
你應(yīng)該可以想到,三個(gè)線程都被創(chuàng)建并start()
,這時(shí)候三個(gè)線程隨時(shí)都可能執(zhí)行run()
方法。因此為了保證run()
執(zhí)行的順序性,我們肯定需要一個(gè)信號量來讓線程知道在任意時(shí)刻能不能執(zhí)行邏輯代碼。
另外,因?yàn)槿齻€(gè)線程是獨(dú)立的,這個(gè)信號量的變化肯定需要對其他線程透明,因此volatile關(guān)鍵字也是必須要的。
public class TicketExample2 { //信號量 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(); } //信號量變更 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
此種方法的思想跟第三種方法是一樣的,都是不考慮線程執(zhí)行的順序而是考慮用一些方法控制線程執(zhí)行業(yè)務(wù)邏輯的順序。這里我們同樣用一個(gè)原子類型信號量ticket
,當(dāng)然你可以不用原子類型,這里我只是為了保證自增操作的線程安全。然后我們用了一個(gè)可重入鎖ReentrantLock
。用來給方法加鎖,當(dāng)一個(gè)線程拿到鎖并且標(biāo)識位正確的時(shí)候開始執(zhí)行業(yè)務(wù)邏輯,執(zhí)行完畢后喚醒下一個(gè)線程。
這里我們不需要使用while進(jìn)行自旋操作了,因?yàn)長ock可以讓我們喚醒指定的線程,所以改成if就可以實(shí)現(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(); //因?yàn)榫€程的執(zhí)行順序是不可預(yù)期的,因此需要每個(gè)線程自旋 System.out.println("線程" + name + " 開始執(zhí)行"); if(ticket.get() != name) { try { System.out.println("當(dāng)前標(biāo)識位為" + 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í)行
當(dāng)前標(biāo)識位為1,線程2 開始等待
線程1 開始執(zhí)行
1
線程3 開始執(zhí)行
當(dāng)前標(biāo)識位為2,線程3 開始等待
線程2 被喚醒
2
線程3 被喚醒
3
上述的執(zhí)行結(jié)果并非唯一,但可以保證打印的順序一定是123這樣的順序。
以上是“Java中怎么保證線程順序執(zhí)行”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。