前言
昨晚老東家微信群里一堆前同事充滿興致的在討論一道據說是阿里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無限循環優雅一些了 (><)
本次“注水”博文到此結束,謝謝觀看!