從一道阿里面試題說起


前言

        昨晚老東家微信群里一堆前同事充滿興致的在討論一道據說是阿里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無限循環優雅一些了 (><)

本次“注水”博文到此結束,謝謝觀看!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM