圖學java基礎篇之并發
概述
并發處理本身就是編程開發重點之一,同時內容也很繁雜,從底層指令處理到上層應用開發都要涉及,也是最容易出問題的地方。這塊知識也是評價一個 開發人員水平的重要指標,本人自認為現在也只是學其皮毛,因此本文重點介紹java的并發相關體系,具體的點懂得就多講,不懂得就給出參考文章。先來看 圖:
本文重點介紹jdk中concurrent的內容,并發相關基礎不在介紹,如果想系統的學習,建議直接看并發大作《java并發編程實踐》吧(之前看過,可惜很多地方沒懂),博客終究只是快餐,增強學習拓展知識很好,但打基礎還是要看書實戰的。
</div>java內存模型
直接盜圖一張(圖寢刪),詳細講解參考 博客 。
java內存模型的作用可以理解為抽象了線程私有內存與主存(共享內存或堆)的關系,也就是原子性、可見性、順序性的原則,而后介紹的內容都是為了保證這些原則的實現手段。
</div>實現多線程的方法
這一塊其實不必多說,大家都很熟悉,這里簡單對比下優缺點:
- 繼承Thread:由于java不支持多繼承,所以用起來有很強的局限性,使用場景不多。
- 實現Runnable接口:常用的方式,有點對比Thread,并且更方便實現資源共享( 兩者異同 ,重點在評論)
- 實現Callable接口: 結合Future使用,可以獲取線程執行結果
- Executor:concurrent中提供的一個上層并發處理框架,底層也是基于上邊三種實現,基于此實現了線程池、任務調度等類,為一些典型場景提供了便捷的實現手段。 </ul>
- volatile:volatile保證了原子操作在線程間的可見性(注意僅能保證可見性),并且修飾對象的操作不會指令重排。對于一個原子操作可以保證其之間一致,但是原子操作真的很少,比如i++都不行。更多介紹
- Atomic:原子類,,基于 CAS 原理實現,提供了基本類型和引用對應的類,能保證其基本操作的可見性,底層基于volatile和Unsafe類(一個線程安全相關類,可以調用底層 native方法)。如果線程間的同步僅限于某個值的改變,則可考慮用該類(實際上多數時候即使適合用也能找到對應的上層封裝類,而不必自己實現)
- synchronized:最為常用的同步方法之一,可以分為同步方法和同步代碼塊兩類,使用簡單,唯一一定要搞清的是synchronized鎖的對象是誰。(拓展: synchronized底層實現 )
- wait/notify:同步幾大原語之二(記得還有join吧),java的Object中已實現的native方法,常用來同步線程執行順序或進度,然而多數場景concurrent也提供了對應工具類,所以一般使用時也應該優先使用對應的工具類
- Lock:鎖,主要有ReentrantLock和ReadWriteLock,該方式較synchronized的優勢就是使用比較靈活,同步不再局限于代碼塊或者方法,可以在任何需要的地方加鎖解鎖。就性能方面,除非你用的是1.4之前的jdk,否則兩者差異不大
- ThreadLocal:線程本地變量,其內部是一個map,key為線程對象本身,value為對應變量的一個拷貝,每個線程使用該變量時實際使用的是其副本,以此解決多線程共享變量的競爭, 需要注意的是改類型并不是解決同步問題的,而是解決資源共享問題的,每個線程使用各自的副本,相互之間不影響,但是該變量的值變化相互之間也是隔離的 ThreadLocal深入剖析 </ul>
- 原子更新基本類型類,提供了AtomicBoolean、AtomicInteger和AtomicLong,對于其他基本類型,可以參照其實現自行通過Unsafe的方法實現(實質上都是轉成int處理)
- 原子更新數組類:也提供了三種類型:AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray
- 原子更新引用類型:對于非基本類型提供的類,AtomicReference、AtomicReferenceFieldUpdater、AtomicMarkableReference
- 原子更新字段類:這幾個類主要用于更新類中的某個字段:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference
這些類使用方法類似,都是通過以下方法實現原子更新: - getAndSet(v):設置新值,返回舊值
- compareAndSet(expectedValue, newValue):如果當前值(current value)等于期待的值(expectedValue), 則原子地更新指定值為新值(newValue), 如果更新成功,返回true, 否則返回false, 換句話可以這樣說: 將原子變量設置為新的值, 但是如果從我上次看到的這個變量之后到現在被其他線程修改了(和我期望看到的值不符), 那么更新失敗
詳細的使用可以參考 Java中的Atomic包使用指南
</ul>
- ReentrantLock:重入鎖,其底層通過一個Sync的靜態類實現加鎖解鎖。Sync繼承自 AbstractQueuedSynchronizer,該抽象類內部實現了一個鏈表,保存了請求獲取鎖的狀態,同時其內部有一個狀態值,用來表示鎖是否 被線程獲取,其修改通過Unsafe包提供的CAS方法,以此可以保證其可見性。同時尤其內部實現也可以知道,之所以叫重入鎖,是因為其提供了非阻塞的使 用方式,如果使用tryLock方法,當lock時在發現鎖已lock的情況下,會立即返回結果,而不會阻塞。 使用示例
- ReentrantReadWriteLock:可重入讀寫鎖。實現原理和ReentrantLock一樣,再次基礎上增加了讀寫鎖的操作。使用示例
- Condition:實現類是AbstractQueuedSynchronizer中的一個內部類,其功能是實現線程間的協調通信,使得某個,或者某些 線程一起等待某個條件(Condition),只有當該條件具備( signal 或者 signalAll方法被帶調用)時 ,這些等待線程才會被喚醒,從而重新爭奪鎖。 使用示例
- LockSupport:可以看做對Unsafe包中并發原語的封裝,為上層的鎖實現提供原語操作。一般情況下上層開發很少用到。 </ul>
- Callable、Future:前邊已經介紹過了,提供了一種可以獲取返回值的線程實現方法,一般與
ExecutorService配合使用。Callable使用 - Executor:線程工具類,主要用于線程池、ThreadFactory、Callable實例。 Executors詳解
- ThreadFactory:接口類,提供了創建線程個工廠類,多是時候配合線程池,作為參數傳入為線程池提供創建線程的方法。
- ExecutorService:接口類,是線程池實現類ThreadPoolExecutor的基類。
- ExecutorCompletionService:與ExecutorService功能一樣,不過其提供了poll()和take()兩個方法用于獲取線程執行結果,前者是非阻塞的,后者是阻塞的。
使用示例
</ul>
- ForkJoinPool:和ExecutorService類似,不過其提供了fork和join的功能,能夠將池內指定級別的任務進行分解或合并。這種處理方式與目前很多流處理框架類似,不過該實現使用的不多。
- CountDownLatch:CountDownLatch 是一個線程協調器,它允許一個或多個線程等待一系列指定操作的完成。
CountDownLatch 以一個給定的數量初始化。countDown() 每被調用一次,這一數量就減一。通過調用await() 方法之一,線程可以阻塞等待這一數量到達零。 - CyclicBarrier:CyclicBarrier類也是一種同步機制,它可以在指定位置設定barrier,只有所有線程都執行到該位置是,才會繼續向下執行。
- Exchanger:該類提供了線程間交換數據的方法,可以視作一個管道。 實現原理
- Semaphore:信號量,學過操作系統的都知道,實現生產者—消費者的常用手段之一,信號量最大的用處是可以控制資源的訪問數量,比起阻塞隊列更加靈活。使用示例 </ul>
實現同步的方法
這一塊也是常用的,也僅對比介紹一下:
concurrent包
JDK 1.5增加了java.util.concurrent包,其內部提供了大量并發相關類,大大簡化了設計并發的程序開發。該包大致可分為四大 塊:Atomic、Lock、Executor、以及線程安全的集合類,由于集合類已經在該系列第一篇介紹過,因此這里重點介紹前三塊,此外還有一些工具 類,這里僅介紹幾個常見的。
Atomic
前邊已經介紹過,Atomic為修飾的對象提供了原子更新,保證了其更新在線程間的可見性,Atomic包內的原子類實現主要基于CAS原理,利用了Unsafe包提供的CAS方法,其中可以分為以下幾類:
Lock
Lock提供了一種更為靈活的同步方式,Lock下主要有以下類:
Executor
Executor框架是concurrent中最常用的內容之一,其提供了一套能夠便捷管理線程和任務的類,使我們能夠很方便創建、調度一批具有同類操作的線程。其主要有以下幾個類(接口):
other
此外concurrent包還提供了一些其他類,這里僅列出功能,具體的使用可自行查詢,這里給出一個比較完善的總結 java.util.concurrent 用戶指南