從一道阿里面試題說起
前言
昨晚老東家微信群裡一堆前同事充滿興致的在討論一道據說是阿里P7的面試題,不管題目來源是不是真的,但題目本身卻比較有意思,虛虛實實去繁化簡,卻能看出一個人對Java知識掌握的深度以及靈活度。
閒話少敘,咱們直接“上菜”。
正文
1、原始碼如下所示,問執行之後列印的數是什麼?
1 static Integer count = 0; 2 public static void main(String[] args) { 3 for (int i = 0; i < 1000; i++) { 4 new Thread(() -> { 5 synchronized (count) { 6 count++; 7 } 8 }).start(); 9 } 10 11 System.out.println(count); 12 }
相信只要對多執行緒的執行機制有了解的道友應該都會知道,上文中的同步塊只是一個幌子,因為這一千個子執行緒不一定都會在main方法所在的主執行緒執行到第11行時都執行完,跟同步塊沒有半毛錢關係。所以第11行輸出的結果是從0到1000不等的(理論上會出現的結果範圍,實際很難出現)。
2、以上面的為基礎,延伸一下呢,比如加個while迴圈後最終列印的又是什麼?
1 static Integer count = 0; 2 public static void main(String[] args) { 3 for (int i = 0; i < 1000; i++) { 4 new Thread(() -> { 5 synchronized (count) { 6 count++; 7 } 8 }).start(); 9 } 10 11 while (true) { 12 System.out.println(count); 13 } 14 }
首先我們需要知道count++這種操作是非原子操作;其次我們需要了解synchronized同步塊的作用機制。
synchronized同步是對一個物件加鎖,如果synchronized加在非靜態方法上,鎖的是當前物件例項;如果加在靜態方法上,鎖的是當前類的Class物件;如果是一個單獨的塊,鎖的就是括號後面的物件。可知此處是同步塊,鎖的就是count這個Integer物件了。
如果我們的知識掌握到這裡,得出的答案就是1000了,因為同步塊能保證多個執行緒對同一個物件的操作是順序執行的。但是實際執行的時候,你會發現很多時候最終列印的資料不是1000,是999或者998這種數,那這是為什麼呢?
其中的關鍵就出在count這個物件身上。synchronized實現的是對同一個物件加鎖,但看一下Integer原始碼你會發現,它是final型別的,就是說當你對它進行+1的操作之後,得到的這個新的count物件已經不是之前的count物件了。既然鎖的物件都不一樣,自然就不會觸發synchronized的同步機制了。
至此可以看出,本題目不知考查了對同步塊的理解,還附帶了對jdk原始碼的考查。另,java中的裝包類,都是final型別的。
後記
到此本應結束,但我後來覺得用while無限迴圈這種方式獲取主執行緒的最終執行結果有點蠢,於是我給改造了一下:
1 static Integer count = 0; 2 public static void main(String[] args) throws InterruptedException { 3 for (int i = 0; i < 1000; i++) { 4 Thread thread = new Thread(() -> { 5 synchronized (count) { 6 count++; 7 } 8 }); 9 thread.start(); 10 thread.join(); 11 } 12 13 System.out.println(count); 14 }
用join來確保主執行緒最後執行(可參照博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/10921870.html 瞭解join方法的作用),但是執行完之後,發現結果總是1000。待檢查一番之後才恍然,
此處用join方法是不合適的。因為當主執行緒執行到thread.join()這一行之後,正常的話會繼續執行for迴圈的下一次迴圈,但是由於被子執行緒join了,所以需先執行完這個子執行緒才能繼續走下一次for迴圈,這樣造成的效果就是這一千個執行緒都是順序啟動順序執行,不存在並發現象,所以結果也就都是1000了。可以發現,利用join有時也能做到同步的效果。
既然join方法不行,那就用併發包中的CountDownLatch吧。
1 static Integer count = 0; 2 public static void main(String[] args) throws InterruptedException { 3 CountDownLatch countDownLatch = new CountDownLatch(1000); 4 for (int i = 0; i < 1000; i++) { 5 new Thread(() -> { 6 synchronized (count) { 7 count++; 8 countDownLatch.countDown(); 9 } 10 }).start(); 11 } 12 countDownLatch.await(); 13 System.out.println(count); 14 }
這樣就比while無限迴圈優雅一些了 (><)
本次“注水”博文到此結束,謝謝觀看!