您好,登錄后才能下訂單哦!
這篇文章主要講解了“java怎么實現兩個線程按順序交替輸出1-100”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“java怎么實現兩個線程按順序交替輸出1-100”吧!
有了上面的思路,你肯定能快速寫出以下代碼:
public class PrintNumber extends Thread { private static int cnt = 0; private int id; // 線程編號 public PrintNumber(int id) { this.id = id; } @Override public void run() { while (cnt < 100) { while (cnt%2 == id) { cnt++; System.out.println("thread_" + id + " num:" + cnt); } } } public static void main(String[] args) { Thread thread0 = new PrintNumber(0); Thread thread1 = new PrintNumber(1); thread0.start(); thread1.start(); } }
但當你實際運行后會發現!!!
thread_0 num:1 thread_0 num:3 thread_1 num:3 thread_1 num:5 thread_1 num:6 thread_0 num:5 thread_0 num:8 thread_0 num:9 thread_1 num:8 thread_0 num:11 thread_1 num:11 .........
不僅順序不對,還有重復和丟失!問題在哪?回到代碼中cnt++; System.out.println("thread_" + id + " num:" + cnt);
這兩行,它主要包含兩個動作,cnt++
和輸出,當cnt++執行完成后可能就已經觸發了另一個線程的輸出。簡化下執行流程,每個時刻JVM有4個動作要執行。
thread_0 cnt++
thread_0 print
thread_1 cnt++
thread_1 print 根據Java as-if-serial語義,jvm只保證單線程內的有序性,不保證多線程之間的有序性,所以上面4個步驟的執行次序可能是 1 2 3 4,也可能是1 3 2 4,更可能是1 3 4 2,對于上面的代碼而言就是最終次序可能會發生變化。另外,cnt++ 可以拆解為兩行底層指令,tmp = cnt + 1; cnt = tmp
,當兩個線程同時執行上述指令時就會面臨和1 2 3 4步驟同樣的問題,…… 沒錯,多線程下的行為,和你女朋友的心思一樣難以琢磨。 如何解決這個問題?解決方案本質上都是保證代碼執行順和我們預期的一樣就行,正確的解法一和后面幾個解法本質上都是同樣的原理,只是實現方式不一樣。
解法一正確的代碼如下:
public class PrintNumber extends Thread { private static AtomicInteger cnt = new AtomicInteger(); private int id; public PrintNumber(int id) { this.id = id; } @Override public void run() { while (cnt.get() <= 100) { while (cnt.get()%2 == id) { System.out.println("thread_" + id + " num:" + cnt.get()); cnt.incrementAndGet(); } } } public static void main(String[] args) { Thread thread0 = new PrintNumber(0); Thread thread1 = new PrintNumber(1); thread0.start(); thread1.start(); } }
上面代碼通過AtomicInteger的incrementAndGet方法將cnt++的操作變成了一個原子操作,避免了多線程同時操作cnt導致的數據錯誤,另外,while (cnt.get()%2 == id
也能保證只有單個線程才能進入while循環里執行,只有當前線程執行完inc后,下一個線程才能執行print,所以這個代碼是可以滿足我們交替輸出的需求的。 但是,這種方法很難駕馭,如果說我吧run函數寫成下面這樣:
@Override public void run() { while (cnt.get() <= 100) { while (cnt.get()%2 == id) { cnt.incrementAndGet(); System.out.println("thread_" + id + " num:" + cnt.get()); } } }
只需要把print和cnt.incrementAndGet()換個位置,結果就完全不一樣了,先inc可能導致在print執行前下一個線程就進入執行改變了cnt的值,導致結果錯誤。另外這種方法其實也不是嚴格正確的,如果不是print而是其他類似的場景,可能會出問題,所以這種寫法強烈不推薦。
事實上,我們只需要cnt++和print同時只有一個線程在執行就行了,所以我們可以簡單將方法一中錯誤的方案加上synchronized即可,代碼如下:
public class PrintNumber extends Thread { private static int cnt = 0; private int id; // 線程編號 public PrintNumber(int id) { this.id = id; } @Override public void run() { while (cnt <= 100) { while (cnt%2 == id) { synchronized (PrintNumber.class) { cnt++; System.out.println("thread_" + id + " num:" + cnt); } } } } public static void main(String[] args) { Thread thread0 = new PrintNumber(0); Thread thread1 = new PrintNumber(1); thread0.start(); thread1.start(); } }
這里我用了synchronized關鍵詞將cnt++和print包裝成了一個同步代碼塊,可以保證只有一個線程可以執行。這里不知道有沒有人會問,cnt需不需要聲明為volatile,我的回答是不需要,因為synchronized可以保證可見性。
大家有沒有發現,我上面代碼中一直都用了while (cnt.get()%2 == id)
來判斷cnt是否是自己要輸出的數字,這就好比兩個小孩輪流報數,每個小孩都要耗費精力時不時看看是否到自己了,然后選擇是否報數,這樣顯然太低效了。能不能兩個小孩之間相互通知,一個小孩報完就通知下另一個小孩,然后自己休息,這樣明顯對雙方來說損耗的精力就少了很多。如果我們代碼能有類似的機制,這里就能損耗更少的無用功,提高性能。
這就得依賴于java的wait和notify機制,當一個線程執行完自己的工作,然后喚醒另一個線程,自己去休眠,這樣每個線程就不用忙等。代碼改造如下,這里我直接去掉了while (cnt.get()%2 == id)
。
@Override public void run() { while (cnt <= 100) { synchronized (PrintNumber.class) { cnt++; System.out.println("thread_" + id + " num:" + cnt); PrintNumber.class.notify(); try { PrintNumber.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
能用synchronized的地方就能用ReentrantLock,所以解法三和解法二本質上是一樣的,就是把synchronized換成了lock而已,然后把wait和notify換成Condition的signal和await,改造后的代碼如下:
public class PrintNumber extends Thread { private static Lock lock = new ReentrantLock(); private static Condition condition = lock.newCondition(); private int id; private static int cnt = 0; public PrintNumber(int id) { this.id = id; } private static void print(int id) { } @Override public void run() { while (cnt <= 100) { lock.lock(); System.out.println("thread_" + id + " num:" + cnt); cnt++; condition.signal(); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } lock.unlock(); } } public static void main(String[] args) { Thread thread0 = new PrintNumber(0); Thread thread1 = new PrintNumber(1); thread0.start(); thread1.start(); } }
感謝各位的閱讀,以上就是“java怎么實現兩個線程按順序交替輸出1-100”的內容了,經過本文的學習后,相信大家對java怎么實現兩個線程按順序交替輸出1-100這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。