易燃物匯聚 YouTube 有聲書與書評
Java併發編程之美 封面

Java併發編程之美

翟陸續 薛賓田

1.3萬人在讀
25.5萬字總字數
¥62.3定價

內容簡介

Java併發編程無處不在,涉及的知識點多,要掌握並用好它並非易事。作者加多擁有在大型互聯網公司阿里巴巴的豐富工作經驗,遇到並解決了業務場景中很多實際的併發問題。本書是他對自己實踐經驗的總結與昇華。爲幫助讀者解決學習中的各類痛點,作者將全書明確地分爲基礎篇、高級篇和實踐篇,脈絡清晰;全書以代碼說話,輔以圖表,讓初學者能一步一步地深入堂奧,掌握併發編程的精髓。

讀者書評

★★★★★

本書主要講述Java併發相關知識,內容包括以下幾個部分: 1. 線程基礎 2. 關鍵字 3. ThreadLocal類 4. CAS與AQS 5. 原子操作類 6. Java鎖機制 7. Java併發容器 8. Java同步器 9. 線程池ThreadPoolExecutor 線程基礎 1. 一個JVM的進程中,main函數所在的線程就是被稱爲主線程。 2. 線程調用Object.wait()方法時,該調用線程會被阻塞掛起,直到發生下面幾件事情之一才返回: 其他線程調用了該共享對象的notify()或者notifyAll()方法 其他線程調用了該線程的interrupt()方法,該線程拋出InterruptedException異常返回。 3. 如果調用wait()的線程沒有事先獲取該對象的監視器鎖,則調用wait()方法時調用線程會拋出IllegalMonitorStateException異常。該崩潰是一個常見崩潰。 4. 當前線程調用共享變量的wait()方法後只會釋放當前共享變量上的鎖,如果該線程還持有其他共享變量的鎖,則這些鎖是不會被釋放的。在使用的時候,這裏可能導致死鎖的發生。 5. 調用notify()方法後,會喚醒一個在該共享變量上調用wait系列方法後被掛起的線程。一個共享變量上可能會有多個線程在等待,具體喚醒哪個等待的線程是隨機的。 6. notifyAll()方法只會喚醒調用這個方法前調用了wait系列函數而被放入共享變量等待集合裏面的線程。如果調用notifyAll()方法後一個線程調用了該共享變量的wait()方法而被放入阻塞集合,則該線程是不會被喚醒的。 7. Thread.join()方法,用於等待某個線程結束運行,調用者線程會被阻塞。 8. Thread.sleep()方法,調用線程會暫時讓出指定時間的執行權,也就是在這期間不參與CPU的調度,但是該線程所擁有的監視器資源,比如鎖還是持有不讓出的。調用線程在sleep時間到了,或者被中斷時,會繼續參與調度。 9. Thread.yield()方法是在暗示線程調度器當前線程請求讓出自己的CPU使用,但是線程調度器可以無條件忽略這個暗示。 10. sleep與yield區別:當線程調用sleep方法時調用線程會被阻塞掛起指定的時間,在這期間線程調度器不會去調度該線程。而調用yield方法時,線程只是讓出自己剩餘的時間片,並沒有被阻塞掛起,而是處於就緒狀態,線程調度器下一次調度時就有可能調度到當前線程執行。 11. Thread.interrupt() 僅僅是設置標誌,線程實際並沒有被中斷,它會繼續往下執行。如果線程A因爲調用了wait系列函數、join方法或者sleep方法而被阻塞掛起,這時候若線程B調用線程A的interrupt()方法,線程A會在調用這些方法的地方拋出InterruptedException異常而返回。 12. 當前線程使用完時間片後,就會處於就緒狀態並讓出CPU讓其他線程佔用,這就是上下文切換,從當前線程的上下文切換到了其他線程。在編程時,要避免頻繁的進行線程上下文切換。 13. Java中的線程分爲兩類,分別爲daemon線程(守護線程)和user線程(用戶線程)。main線程運行結束後,JVM會自動啓動一個叫作DestroyJavaVM的線程,該線程會等待所有用戶線程結束後終止JVM進程。 關鍵字 1. synchronized是Java提供的一種原子性內置鎖,Java中的每個對象都可以把它當作一個同步鎖來使用,這些Java內置的使用者看不到的鎖被稱爲內部鎖,也叫作監視器鎖。 2. synchronized的內存語義是:把在synchronized塊內使用到的變量從線程的工作內存中清除,這樣在synchronized塊內使用到該變量時就不會從線程的工作內存中獲取,而是直接從主內存中獲取。退出synchronized塊的內存語義是把在synchronized塊內對共享變量的修改刷新到主內存。 3. volatile變量:線程在寫入該變量時,不會把值緩存在寄存器或者其他地方,而是會把值刷新回主內存。當其他線程讀取該共享變量時,會從主內存重新獲取最新值,而不是使用當前線程的工作內存中的值。 4. volatile雖然提供了可見性保證,但並不保證操作的原子性。volatile還有一個語義是:避免指令重排。 ThreadLocal類 1. ThreadLocal是線程本地變量,訪問這個變量的每個線程都會有這個變量的一個本地副本。當多個線程操作這個變量時,實際操作的是自己本地內存裏面的變量,從而避免了線程安全問題。 2. ThrealLocal原理:Thread類中有一個threadLocals和一個inheritableThreadLocals,它們都是ThreadLocalMap類型,而ThreadLocalMap是一個定製化的Hashmap。在默認情況下,每個線程中的這兩個變量都爲null,只有當前線程第一次調用ThreadLocal的set或者get方法時纔會創建它們。每個線程的本地變量都是存放在調用線程的threadLocals變量裏面。也就是說,ThreadLocal類型的本地變量存放在具體的線程內存空間中。 3. 本地變量存放在線程自己的內存變量threadLocals中,如果當前線程一直不消亡,那麼這些本地變量會一直存在,所以可能會造成內存溢出,因此使用完畢後要記得調用ThreadLocal的remove方法刪除對應線程的threadLocals中的本地變量。 4. ThreadLocal不支持繼承性,如果希望子類繼承,也需要利用InheritableThreadLocal。當父線程創建子線程時,構造函數會把父線程中inheritableThreadLocals變量裏面的本地變量複製一份保存到子線程的inheritableThreadLocals變量裏面。 5. ThreadLocalRandom類是JDK 7在JUC包下新增的隨機數生成器,它彌補了Random類在多線程下的缺陷。 CAS 1. CAS即Compare and Swap,其是JDK提供的非阻塞原子性操作,它通過硬件保證了比較—更新操作的原子性。 2. CAS的侷限性: CAS會存在ABA問題 CAS操作一般都在一個無限循環裏面調用,直到操作成功才返回。如果併發量大,變量被頻繁修改,則會導致效率低下。 CAS只能對一個變量進行原子操作。 3. CAS可被看作是樂觀鎖的一種。它提供了非阻塞的原子性操作。 4. CAS利用sun.misc.Unsafe類實現,Unsafe核心方法都是native方法,這些native方法最終都是調用Atomic::cmpxchg,該函數利用匯編實現。單核處理器利用cmpxchgl指令實現,多核處理器利用lock前綴的cmpxchgl實現。 5. 只有Bootstrap類加載器加載的Class才能調用Unsafe類。 AQS 1. AbstractQueuedSynchronizer抽象同步隊列簡稱AQS,它是實現同步器的基礎組件,併發包中鎖的底層就是使用AQS實現的。 2. 在AQS中維持了一個單一的狀態信息state,可以通過getState、setState、compareAndSetState函數修改其值。在不同的鎖中,該變量含義不一樣。 3. ReentrantLock的實現來說,state表示當前線程獲取鎖的可重入次數;對於讀寫鎖ReentrantReadWriteLock來說,state的高16位表示讀狀態,也就是獲取該讀鎖的次數,低16位表示獲取到寫鎖的線程的可重入次數;對於semaphore來說,state用來表示當前可用信號的個數;對於CountDownlatch來說,state用來表示計數。 4. AQS有個內部類ConditionObject,用來結合鎖實現線程同步。ConditionObject是條件變量,每個條件變量對應一個條件隊列(單向鏈表隊列),其用來存放調用條件變量的await方法後被阻塞的線程,如類圖所示,這個條件隊列的頭、尾元素分別爲firstWaiter和lastWaiter。 5. 在獨佔方式下獲取和釋放資源使用的方法爲: void acquire(int arg) void acquireInterruptibly(int arg) boolean release(int arg)。 6. 在共享方式下獲取和釋放資源的方法爲: void acquireShared(int arg) void acquireSharedInterruptibly(int arg) boolean releaseShared(int arg)。 7. AQS類並沒有提供可用的tryAcquire和tryRelease方法 。tryAcquire和tryRelease需要由具體的子類來實現。 8. synchronized同時只能與一個共享變量的notify或wait方法實現同步,而AQS的一個鎖可以對應多個條件變量。在每個條件變量內部都維護了一個條件隊列,用來存放調用條件變量的await()方法時被阻塞的線程。 9. AQS只提供了ConditionObject的實現,並沒有提供newCondition函數,該函數用來new一個ConditionObject對象。需要由AQS的子類來提供newCondition。 原子操作類 1. JUC併發包都是基於CAS實現。其中包含有AtomicInteger、AtomicLong和AtomicBoolean等原子性操作類,底層也都是基於CAS實現。 2. AtomicLong的性能瓶頸是由於過多線程同時去競爭一個變量的更新而產生的。LongAdder用來克服在高併發下使用AtomicLong的缺點。 3. LongAdder原理:LongAdder在內部維護多個Cell變量,每個Cell裏面有一個初始值爲0的long型變量,這樣,在同等併發量的情況下,爭奪單個變量更新操作的線程量會減少,這變相地減少了爭奪共享資源的併發量。另外,多個線程在爭奪同一個Cell原子變量時如果失敗了,它並不是在當前Cell變量上一直自旋CAS重試,而是嘗試在其他Cell的變量上進行CAS嘗試,這個改變增加了當前線程重試CAS成功的可能性。最後,在獲取LongAdder當前值時,是把所有Cell變量的value值累加後再加上base返回的。 4. LongAdder維護了一個延遲初始化的原子性更新數組(默認情況下Cell數組是null)和一個基值變量base。由於Cells佔用的內存是相對比較大的,所以一開始並不創建它,而是在需要時創建,也就是惰性加載。 5. LongAdder類是LongAccumulator的一個特例,LongAccumulator比LongAdder的功能更強大。 Java鎖機制 1. 悲觀鎖:指對數據被外界修改持保守態度,認爲數據很容易就會被其他線程修改,所以在數據被處理前先對數據進行加鎖,並在整個數據處理過程中,使數據處於鎖定狀態。 2. 樂觀鎖:樂觀鎖是相對悲觀鎖來說的,它認爲數據在一般情況下不會造成衝突,所以在訪問記錄前不會加排它鎖,而是在進行數據提交更新時,纔會正式對數據衝突與否進行檢測。 3. 公平鎖與非公平鎖:根據線程獲取鎖的搶佔機制,鎖可以分爲公平鎖和非公平鎖,公平鎖表示線程獲取鎖的順序是按照線程請求鎖的時間早晚來決定的,也就是最早請求鎖的線程將最早獲取到鎖。而非公平鎖則在運行時闖入,也就是先來不一定先得。 4. 獨佔鎖與共享鎖:獨佔鎖保證任何時候都只有一個線程能得到鎖,ReentrantLock就是以獨佔方式實現的。共享鎖則可以同時由多個線程持有,例如ReadWriteLock讀寫鎖,它允許一個資源可以被多線程同時進行讀操作。 5. 可重入鎖:當一個線程要獲取一個被其他線程持有的獨佔鎖時,該線程會被阻塞,那麼當一個線程再次獲取它自己已經獲取的鎖時,如果不被阻塞,那麼我們說該鎖是可重入的。 6. 自旋鎖:當前線程在獲取鎖時,如果發現鎖已經被其他線程佔有,它不馬上阻塞自己,在不放棄CPU使用權的情況下,多次嘗試獲取(默認次數是10,可以使用-XX:PreBlockSpinsh參數設置該值),很有可能在後面幾次嘗試中其他線程已經釋放了鎖。如果嘗試指定的次數後仍沒有獲取到鎖則當前線程纔會被阻塞掛起。由此看來自旋鎖是使用CPU時間換取線程阻塞與調度的開銷。 7. synchronized內部鎖是可重入鎖。可重入鎖的原理是在鎖內部維護一個線程標示,用來標示該鎖目前被哪個線程佔用,然後關聯一個計數器。一開始計數器值爲0,說明該鎖沒有被任何線程佔用。當一個線程獲取了該鎖時,計數器的值會變成1,這時其他線程再來獲取該鎖時會發現鎖的所有者不是自己而被阻塞掛起。 8. ReentrantLock是可重入的獨佔鎖,同時只能有一個線程可以獲取該鎖,其他獲取該鎖的線程會被阻塞而被放入該鎖的AQS阻塞隊列裏面。 9. ReentrantReadWriteLock採用讀寫分離的策略,允許多個線程可以同時獲取讀鎖。讀寫鎖的內部維護了一個ReadLock和一個WriteLock,它們依賴Sync實現具體功能。而Sync繼承自AQS,並且也提供了公平和非公平的實現。 10. StampedLock是併發包裏面JDK8版本新增的一個鎖,該鎖提供了三種模式的讀寫控制,當調用獲取鎖的系列函數時,會返回一個long型的變量,我們稱之爲戳記(stamp),這個戳記代表了鎖的狀態。其中try系列獲取鎖的函數,當獲取鎖失敗後會返回爲0的stamp值。當調用釋放鎖和轉換鎖的方法時需要傳入獲取鎖時返回的stamp值。 11. StampedLock的三種模式: 寫鎖writeLock:是一個排它鎖或者獨佔鎖,某時只有一個線程可以獲取該鎖,當一個線程獲取該鎖後,其他請求讀鎖和寫鎖的線程必須等待,這類似於ReentrantReadWriteLock的寫鎖 悲觀讀鎖readLock:是一個共享鎖,在沒有線程獲取獨佔寫鎖的情況下,多個線程可以同時獲取該鎖。 樂觀讀鎖tryOptimisticRead:它是相對於悲觀鎖來說的,在操作數據前並沒有通過CAS設置鎖的狀態,僅僅通過位運算測試。 12. StampedLock的讀寫鎖都是不可重入鎖,所以在獲取鎖後釋放鎖前不應該再調用會獲取鎖的操作,以避免造成調用線程被阻塞。當多個線程同時嘗試獲取讀鎖和寫鎖時,誰先獲取鎖沒有一定的規則,完全都是盡力而爲,是隨機的。 Java併發容器 1. ConcurrentLinkedQueue是線程安全的無界非阻塞隊列,其底層數據結構使用單向鏈表實現,對於入隊和出隊操作使用CAS來實現線程安全。 2. LinkedBlockingQueue使用獨佔鎖實現的阻塞隊列。默認隊列容量爲0x7fffffff,用戶也可以自己指定容量,所以從一定程度上可以說LinkedBlockingQueue是有界阻塞隊列。 3. LinkedBlockingQueue有兩個ReentrantLock的實例,分別用來控制元素入隊和出隊的原子性,其中takeLock用來控制同時只有一個線程可以從隊列頭獲取元素,其他線程必須等待,putLock控制同時只能有一個線程可以獲取鎖,在隊列尾部添加元素,其他線程必須等待。另外,notEmpty和notFull是條件變量,它們內部都有一個條件隊列用來存放進隊和出隊時被阻塞的線程,其實這是生產者—消費者模型。 4. LinkedBlockingQueue的內部是通過單向鏈表實現的,使用頭、尾節點來進行入隊和出隊操作,也就是入隊操作都是對尾節點進行操作,出隊操作都是對頭節點進行操作。 5. ArrayBlockingQueue使用有界數組方式實現的阻塞隊列。 6. 相比LinkedBlockingQueue, ArrayBlockingQueue的size操作的結果是精確的,因爲計算前加了全局鎖。 7. PriorityBlockingQueue是帶優先級的無界阻塞隊列,每次出隊都返回優先級最高或者最低的元素。其內部是使用平衡二叉樹堆實現的,所以直接遍歷隊列元素不保證有序。默認使用對象的compareTo方法提供比較規則,如果你需要自定義比較規則則可以自定義comparators。 8. DelayQueue併發隊列是一個無界阻塞延遲隊列,隊列中的每個元素都有個過期時間,當從隊列獲取元素時,只有過期元素纔會出隊列。隊列頭元素是最快要過期的元素。 9. DelayQueue內部使用PriorityQueue存放數據,使用ReentrantLock實現線程同步。 線程同步器 1. CountDownLatch:CountDownLatch是使用AQS實現的。通過下面的構造函數,你會發現,實際上是把計數器的值賦給了AQS的狀態變量state,也就是這裏使用AQS的狀態值來表示計數器值。 2. CountDownLatch與join區別: 調用一個子線程的join()方法後,該線程會一直被阻塞直到子線程運行完畢,而CountDownLatch則使用計數器來允許子線程運行完畢或者在運行中遞減計數,也就是CountDownLatch可以在子線程運行的任何時候讓await方法返回而不一定必須等到線程結束。 使用線程池來管理線程時一般都是直接添加Runable到線程池,這時候就沒有辦法再調用線程的join方法了,就是說countDownLatch相比join方法讓我們對線程同步有更靈活的控制。 3. 當線程調用CountDownLatch對象的await方法後,當前線程會被阻塞,直到下面的情況之一發生纔會返回: 當所有線程都調用了CountDownLatch對象的countDown方法後,也就是計數器的值爲0時; 其他線程調用了當前線程的interrupt()方法中斷了當前線程,當前線程就會拋出InterruptedException異常,然後返回。 4. CyclicBarrier:迴環屏障,它可以讓一組線程全部達到一個狀態後再全部同時執行。這裏之所以叫作迴環是因爲當所有等待線程執行完畢,並重置CyclicBarrier的狀態後它可以被重用。之所以叫作屏障是因爲線程調用await方法後就會被阻塞,這個阻塞點就稱爲屏障點,等所有線程都調用了await方法後,線程們就會衝破屏障,繼續向下運行。 5. CyclicBarrier基於獨佔鎖實現,本質底層還是基於AQS的。內部有兩個變量parties和count。parties用來記錄線程個數,這裏表示多少線程調用await後,所有線程纔會衝破屏障繼續往下運行。count變量記錄當前需要等待的線程數量。 6. CycleBarrier與CountDownLatch的不同在於,前者是可以複用的,並且前者特別適合分段任務有序執行的場景。 7. Semaphore信號量:它內部的計數器是遞增的,並且在一開始初始化Semaphore時可以指定一個初始值,但是並不需要知道需要同步的線程個數,而是在需要同步的地方調用acquire方法時指定需要同步的線程個數。 8. Semaphore默認採用非公平策略。公平性策略,還是靠hasQueuedPredecessors這個函數來保證的。 好了,就到這裏~

★★★★★

這本書應該是我耗費時間最長的一本書,按照阿里大佬的事例及看源碼一步步跟過來的,從什麼是線程,線程狀態,API,死鎖,線程安全,內存可見性,原子性,關鍵字volatile,synchronized,AQS,CAS,僞共享,lock,併發包中三大同步工具(CountDownLatch,迴環屏障CylicBarrier,信號量Samephone)與join的對比及優化,線程池的創建及注意事項,ConcurrentHashMap在實際項目中的注意事項,SimpleDateFormat的線程不安全問題及解決辦法,使用線程池之後需要釋放資源,ThreadLocal的若引用帶來的OOM問題用完之後需要remove....等等,感恩

★★★★★

【第一幕:田埂遇書】 前日去後生仔屋檐底下避雨,見他案頭擺着本《Java併發編程之美》,封皮上印着"翟陸續、薛賓田"兩個名字,倒像是兩根麻繩捆着半袋陳穀子。我眯眼瞄着目錄裏"線程""鎖""死鎖"的字樣,忽地想起那年趕集撞見兩頭犟驢——一個往東拉磨盤,一個往西拽石碾,最後把驢繮繩都擰成麻花辮。 "老哥你看,這書講的是讓千百頭驢子乖乖拉磨的法子哩!"後生仔不知何時冒出來,手裏還攥着半塊啃剩的芋頭糕。我啐了口旱菸沫:"莫哄俺,驢子聽話還要你拿鞭子抽?" 【第二幕:竈臺說理】 後生仔把書往柴火竈邊一撂,火星子濺到泛黃的紙頁上:"您老見過村口老槐樹沒?樹杈上掛十隻破籮筐,十隻麻雀爭着啄米粒——這就叫併發!"他蘸着米湯在桌上畫圈圈:"Java併發就像咱插秧,要講究個'深淺均勻不傷根'。" 我抄起竹篾編的簸箕比劃:"莫不是教人拿竹竿趕麻雀?"後生仔直搖頭:"您這法子頂多算同步鎖(synchronized),餓死三隻麻雀,撐肥七隻田鼠!"竈膛裏噼啪炸響的火星,倒像是代碼裏亂蹦的NullPointerException。 【第三幕:月光夜話】 三更天打更的梆子響了三響,後生仔還在院裏踱步:"您知道線程池爲啥像咱村水車嗎?"他指着月光下泛銀光的溪流:"流水不爭先,爭的是滔滔不絕。Executor框架就像六叔公那架百年水車,轉得再歡實,也得守着'三不原則'——不空轉、不卡殼、不翻車。" 我咂着旱菸袋突然樂了:"莫不是要給線程唱《天淨沙》?"順口溜道: "枯藤線程老樹, 鎖頭昏鴉爭渡。 線程池裏趕路, 僞共享處, 原子操作如故。" 後生仔拍大腿笑出淚花:"老哥這曲兒比代碼註釋還傳神!您看這僞共享(False Sharing),就像曬穀場上的籮筐挨太近,篩米的婆娘們胳膊肘總撞一塊兒。" 【第四幕:雞鳴收網】 五更雞叫頭遍時,後生仔往我懷裏塞了本包了油紙的書:"您老就當看《西廂記》——表面上張生崔鶯鶯談情說愛,底下藏着十八般武藝。"晨霧裏他指着書脊上"2018"的烙印:"這可是十年陳的'併發祕笈',比咱醃了三冬的酸筍還經得起熬。" 我摸着書頁裏密密麻麻的代碼,忽覺像極了當年在祠堂抄的《千家詩》。正要開口,村口傳來拖拉機突突聲——那聲音倒像是線程飢餓(Starvation)時發出的哀鳴。 【尾聲:田壟新悟】 如今我插秧總帶着這本書,遇着秧苗倒伏就想起"線程安全",看見螞蟥吸血就記起"內存泄漏"。前日教三娃子認字,竟把"volatile"念成"忽兒漏",倒比教科書還傳神。想來這併發編程,原與種地同理——講究個春種秋收時序,忌諱貪多嚼不爛。 後生仔說得在理:"你老登把synchronized叫'木門閂',把CAS操作比作'搶繡球',這書啊,就是給咱莊稼漢寫的《農政全書》!"這話糙理不糙,就像咱粵西的艾餈,初嘗扎嘴,細品回甘。 🤣 線程似那穿簾燕,鎖頭如鐵護花鈿。 CAS輕點,僞共享驚散, 望長街車水馬龍喧。 且把代碼作詩篇, 笑看那併發舞翩躚。

★★★☆☆

前面的部分是針對併發的基礎概念的簡單描述,均是點到即止,沒有深入。而且存在一些問題,就是作者沒有考慮讀者的角度。例如在線程死鎖的時候已經提到監視器鎖,單其實讀者在這個時候並未有所瞭解,看起來感覺非常突兀。基礎知識的部分,均是概念然後加上代碼的演示。隨後全書均是對juc的源碼的解讀,解讀過程沉悶,相信很少讀者能看完並從中有所收穫。無疑,作者對JUC相關的源碼都很熟悉,技術功底不錯。但全書沒有看到整個體系,更談不上“之美”。僅推薦需要面試的同學抱佛腳用。

★★★★★

對於我這類沒有太多機會接觸併發編程的開發者而言,能從這本書積累經驗。看多了,知道怎麼做比較好,再進一步去實驗或者實踐。感謝。

★★★★★

近11個小時讀完了本書。從併發編程基礎(線程創建、運行、通知和等待等基本操作,線程中斷,死鎖、上線文切換,內存可見性、cas操作、synchronized關鍵字等) 到Java 併發包中的原子操作類、鎖、併發隊列、線程池等高級主題的講解,再到併發編程的實踐(SimpleDateFormat 、深度複製等) 。每天讀到不同的主題都會有新的收穫、但還有些概念沒有搞清楚比如偏移量、cas的原理等問題。 是一本不錯的書、非常值得一讀。希望過段時間回頭重讀一下。

★★★★★

整體來說,本書還是很推薦看看的,會寫代碼的同時還需要知道代碼底層的運行邏輯。成體系的閱讀能夠幫忙我們更好理解碎片化的知識點,JDK以及優秀的開源框架源碼都是學習的很好方式。 Talk is cheap, show me the code. 平常寫代碼過程中(ps 生產確實遇到了!)可能遇到的併發問題在最後一章都提及到了,通過解析源碼找到問題原因並給出相關的解決措施。有的時候問AI或者Google找答案,回頭才發現這些問題早在燈火闌珊處了。 Go, just go

★★★★★

初步看了幾章,講的通俗易懂,容易吸收,值得推薦!

★★★★★

很nice的一本書,跟着作者一塊閱讀了juc包中的源代碼,對併發場景有了更深入的瞭解。因爲一邊看源代碼一邊讀所以花費了許多的時間,不過收穫頗豐。 感謝duag lea大佬,好多代碼都是他擼的✧٩(ˊωˋ*)و✧;  感謝作者大大,凡是有代碼的地方都寫得很詳細(◦˙▽˙◦)

★★★★★

這是一本相當不錯的書,應該是大多程序員喜聞樂見的。代碼實例更容易實踐。 通讀一遍,仍需針對性的細讀部分章節。推薦大家讀

書籍信息

作者翟陸續 薛賓田
出版社電子工業出版社
出版時間2018-10-01
ISBN9787121349478
分類計算機-編程設計
格式EPUB
完結狀態連載中
章節數81