前言:剛學習了一段機器學習,最近需要重構一個java項目,又趕過來看java。大多是線程代碼,沒辦法,那時候總覺得多線程是個很難的部分很少用到,所以一直沒下決定去啃,那些年留下的坑,總是得自己跳進去填一次。
思路:大概看了線程相關的一些知識,對線程的運行機制,同步機制,以及整個系統都做一個全面的了解。在深入每一個部分去看一下線程及其相關包的源碼做深入了解。
目標:線程,並發包(線程池,並發的數據結構,鎖,原子類)。
通過一些資料的查看最終把目標定位在線程和並發包上,線程是核心,並發包是輔助工具,用於多線程運行時的並發問題。其實這樣看來多線程並沒有很多的東西,支持並發的數據結構用於保證數據的安全性,各種鎖機制用來保證類,對象,方法,屬性的並發安全。它的難點主要是在運用上,各種鎖機制的運用,會給系統帶來負擔,也會給程序性能帶來影響,但是同時又要保證數據的同步。鎖機制使用的強度和位置,直接決定了並發系統的好壞。
java中文API:http://www.javaweb.cc/help/JavaAPI1.6/overview-summary.html,下面具體看一下這些包里面有哪些東西。
主要涉及到下面幾個包:
線程相關:java.lang下面的幾個類
接口摘要 | |
Runnable | Runnable 接口應該由那些打算通過某一線程執行其實例的類來實現。 |
類摘要 |
---|
Thread | 線程 是程序中的執行線程。 |
ThreadGroup | 線程組表示一個線程的集合。 |
ThreadLocal<T> | 該類提供了線程局部 (thread-local) 變量。 |
並發包相關:
1. Java.util.concurrent包
接口摘要 | |
BlockingDeque<E> | 支持兩個附加操作的 Queue ,這兩個操作是:獲取元素時等待雙端隊列變為非空;存儲元素時等待雙端隊列中的空間變得可用。 |
BlockingQueue<E> | 支持兩個附加操作的 Queue ,這兩個操作是:獲取元素時等待隊列變為非空,以及存儲元素時等待空間變得可用。 |
Callable<V> | 返回結果並且可能拋出異常的任務。 |
CompletionService<V> | 將生產新的異步任務與使用已完成任務的結果分離開來的服務。 |
ConcurrentMap<K,V> | 提供其他原子 putIfAbsent、remove、replace 方法的 Map 。 |
ConcurrentNavigableMap<K,V> | 支持 NavigableMap 操作,且以遞歸方式支持其可導航子映射的 ConcurrentMap 。 |
Delayed | 一種混合風格的接口,用來標記那些應該在給定延遲時間之后執行的對象。 |
Executor | 執行已提交的 Runnable 任務的對象。 |
ExecutorService | Executor 提供了管理終止的方法,以及可為跟蹤一個或多個異步任務執行狀況而生成 Future 的方法。 |
Future<V> | Future 表示異步計算的結果。 |
RejectedExecutionHandler | 無法由 ThreadPoolExecutor 執行的任務的處理程序。 |
RunnableFuture<V> | 作為 Runnable 的 Future 。 |
RunnableScheduledFuture<V> | 作為 Runnable 的 ScheduledFuture 。 |
ScheduledExecutorService | 一個 ExecutorService ,可安排在給定的延遲后運行或定期執行的命令。 |
ScheduledFuture<V> | 一個延遲的、結果可接受的操作,可將其取消。 |
ThreadFactory | 根據需要創建新線程的對象。 |
枚舉摘要 | |
TimeUnit | TimeUnit 表示給定單元粒度的時間段,它提供在這些單元中進行跨單元轉換和執行計時及延遲操作的實用工具方法。 |
2. java.util.concurrent.locks包
接口摘要 | |
Condition | Condition 將 Object 監視器方法(wait 、notify 和 notifyAll )分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,為每個對象提供多個等待 set(wait-set)。 |
Lock | Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。 |
ReadWriteLock | ReadWriteLock 維護了一對相關的鎖 ,一個用於只讀操作,另一個用於寫入操作。 |
類摘要 | |
AbstractOwnableSynchronizer | 可以由線程以獨占方式擁有的同步器。 |
AbstractQueuedLongSynchronizer | 以 long 形式維護同步狀態的一個 AbstractQueuedSynchronizer 版本。 |
AbstractQueuedSynchronizer | 為實現依賴於先進先出 (FIFO) 等待隊列的阻塞鎖和相關同步器(信號量、事件,等等)提供一個框架。 |
LockSupport | 用來創建鎖和其他同步類的基本線程阻塞原語。 |
ReentrantLock | 一個可重入的互斥鎖 Lock ,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行為和語義,但功能更強大。 |
ReentrantReadWriteLock | 支持與 ReentrantLock 類似語義的 ReadWriteLock 實現。 |
ReentrantReadWriteLock.ReadLock | ReentrantReadWriteLock.readLock() 方法返回的鎖。 |
ReentrantReadWriteLock.WriteLock | ReentrantReadWriteLock.writeLock() 方法返回的鎖。 |
3. java.util.conturrent.atomic
類摘要 | |
AtomicBoolean | 可以用原子方式更新的 boolean 值。 |
AtomicInteger | 可以用原子方式更新的 int 值。 |
AtomicIntegerArray | 可以用原子方式更新其元素的 int 數組。 |
AtomicIntegerFieldUpdater<T> | 基於反射的實用工具,可以對指定類的指定 volatile int 字段進行原子更新。 |
AtomicLong | 可以用原子方式更新的 long 值。 |
AtomicLongArray | 可以用原子方式更新其元素的 long 數組。 |
AtomicLongFieldUpdater<T> | 基於反射的實用工具,可以對指定類的指定 volatile long 字段進行原子更新。 |
AtomicMarkableReference<V> | AtomicMarkableReference 維護帶有標記位的對象引用,可以原子方式對其進行更新。 |
AtomicReference<V> | 可以用原子方式更新的對象引用。 |
AtomicReferenceArray<E> | 可以用原子方式更新其元素的對象引用數組。 |
AtomicReferenceFieldUpdater<T,V> | 基於反射的實用工具,可以對指定類的指定 volatile 字段進行原子更新。 |
AtomicStampedReference<V> | AtomicStampedReference 維護帶有整數“標志”的對象引用,可以用原子方式對其進行更新。 |
對每個包里的部分類進行結構解析:
1. Java.util.concurrent:這個包里,主要常用到的是數據結構Queue,MAP,List,和線程池
*&*Queue隊列相關的類圖:
*&*List和Set相關的類圖:List和Set在並發包中的實現類有copyOwriteArrayList,CopyOnWriteArraySet,ConcurrentSkipListSet,三個類
*&*Map相關的類圖:並發包中與Map相關的包括ConcurrentHashMap,ConcurrentSkipListMap兩個類
這些都是和數據結構相關的,其中藍色的部分表示的是並發包的內容,灰色部分表示其他包(大部分是util)中的內容。其中省略了一部分內容類和接口,因為很多不常用,而且用的時候一部分也不是作為一個數據結構來用的。這也不是這部分關注的重點。我們主要關注並發包相關的內容。這三張類圖,應該可以讓大家對並發包的數據結構有一個大致的了解,並發包還有一個內容就是線程池。
*&*線程池類圖:線程池重最終有兩個實現類ThreadPoolExeutor和SheduleThreadPoolExeutor.但是我們一般不直接去實現這兩個類去創建一個線程池,我們通常用Exeutors這個類來創建一個線程池,這個類中把線程池的創建和管理進行了封裝,我們只需要運用這個類就可以創建一個線程池並進行管理。另外一個接口ThreadFactory主要是用來創建線程的,實現這個接口我們就擁有了一個線程工廠,創建線程更方便。
2. Java.util.concurrent.Locks:(以下說明摘自API)為鎖和等待條件提供一個框架的接口和類,它不同於內置同步和監視器。該框架允許更靈活地使用鎖和條件,但以更難用的語法為代價
Lock
接口支持那些語義不同(重入、公平等)的鎖規則,可以在非阻塞式結構的上下文(包括 hand-over-hand 和鎖重排算法)中使用這些規則。主要的實現是 ReentrantLock。
ReadWriteLock
接口以類似方式定義了一些讀取者可以共享而寫入者獨占的鎖。此包只提供了一個實現,即 ReentrantReadWriteLock
,因為它適用於大部分的標准用法上下文。但程序員可以創建自己的、適用於非標准要求的實現。
Condition
接口描述了可能會與鎖有關聯的條件變量。這些變量在用法上與使用 Object.wait 訪問的隱式監視器類似,但提供了更強大的功能。需要特別指出的是,單個 Lock 可能與多個 Condition 對象關聯。為了避免兼容性問題,Condition 方法的名稱與對應的 Object 版本中的不同。
AbstractQueuedSynchronizer
類是一個非常有用的超類,可用來定義鎖以及依賴於排隊阻塞線程的其他同步器。
AbstractQueuedLongSynchronizer
類提供相同的功能但擴展了對同步狀態的 64 位的支持。
前面兩者都擴展了類 AbstractOwnableSynchronizer
(一個幫助記錄當前保持獨占同步的線程的簡單類)。LockSupport
類提供了更低級別的阻塞和解除阻塞支持,這對那些實現自己的定制鎖類的開發人員很有用。
3. Java.util.concurrent.atomic:(以下內容摘自API)原子類這部分沒有很復雜的類關系,主要是對基礎的int,long,bolean變量,以及相關的數組和對象引用,提供了原子訪問和更新的類。
原子訪問和更新的內存效果一般遵循以下可變規則,正如 The Java Language Specification, Third Edition (17.4 Memory Model) 中的聲明:
-
- get 具有讀取 volatile 變量的內存效果。
- set 具有寫入(分配)volatile 變量的內存效果。
- 除了允許使用后續(但不是以前的)內存操作,其自身不施加帶有普通的非 volatile 寫入的重新排序約束,lazySet 具有寫入(分配)volatile 變量的內存效果。在其他使用上下文中,當為 null 時(為了垃圾回收),lazySet 可以應用不會再次訪問的引用。
- weakCompareAndSet 以原子方式讀取和有條件地寫入變量但不 創建任何 happen-before 排序,因此不提供與除 weakCompareAndSet 目標外任何變量以前或后續讀取或寫入操作有關的任何保證。
- compareAndSet 和所有其他的讀取和更新操作(如 getAndIncrement)都有讀取和寫入 volatile 變量的內存效果。
除了包含表示單個值的類之外,此包還包含 Updater 類,該類可用於獲取任意選定類的任意選定 volatile 字段上的 compareAndSet 操作
類 AtomicBoolean
、AtomicInteger
、AtomicLong
和 AtomicReference
的實例各自提供對相應類型單個變量的訪問和更新。
類AtomicReferenceFieldUpdater
、AtomicIntegerFieldUpdater
和 AtomicLongFieldUpdater
是基於反射的實用工具,可以提供對關聯字段類型的訪問。它們主要用於原子數據結構中,該結構中同一節點(例如,樹節點的鏈接)的幾個 volatile 字段都獨立受原子更新控制。這些類在如何以及何時使用原子更新方面具有更大的靈活性,但相應的弊端是基於映射的設置較為拙笨、使用不太方便,而且在保證方面也較差。
AtomicIntegerArray
、AtomicLongArray
和 AtomicReferenceArray
類進一步擴展了原子操作,對這些類型的數組提供了支持。這些類在為其數組元素提供 volatile 訪問語義方面也引人注目,這對於普通數組來說是不受支持的。
AtomicMarkableReference
類將單個布爾值與引用關聯起來。例如,可以在數據結構內部使用此位,這意味着引用的對象在邏輯上已被刪除。
AtomicStampedReference
類將整數值與引用關聯起來。例如,這可用於表示與更新系列對應的版本號。
花了兩天時間看着一段的API,稍微整理了一下,大部分內容還是來自API,但是我個人覺得這種東西聽起來很難入門,其實一大部分原因是我們沒用從總體上去把握它,從最開始的接口和類的清單表中可能很多東西沒有接觸過,其實那都不是問題,當看到類圖的時候清楚地看到他們之間的父子關系,其實最終需要我們去掌握的類不是很多。
我個人認為,數據結構(queue,Map,List)這部分是最容易掌握的,因為它的內部機制已經實現了,我們只需要知道在什么場景需要使用它,什么時候的數據需要用這種並發安全的數據結構在經過不斷地使用就能掌握(深入理解當我沒說,哈哈)。線程池這部分也很容易掌握,我們只要熟悉Exeutors類,掌握里面的方法,就足夠熟練的運用對線程池。然后是原子類,原子類訪問的變量是volited修飾的,原子類實際上就是對數據進行了操作的原子性(操作是一個不可分割的整體,ex:updape包括讀取數據,修改數據,寫回數據三個步驟,普通的操作就不能保證update操作的原子性)一致性的封裝,保證了對這些數據操作的時候是線程安全的。最難的應該是在鎖上,加鎖會造成線程阻塞,高並發狀態下是否該用鎖,和在什么地方用鎖,鎖的粒度很關鍵,本來只需要在數據上加鎖,而我們卻加在了方法上,或者對象上,那對性能的影響可能不是一星半點。各種鎖的用法和技巧,帶來的差異,弊端都需要清楚的知道。
希望能給像我一樣還未入門的朋友帶來一點幫助,現在已經基本了解了框架,后面就是對這些部分的深入探索,從具體的應用中看差異,深入源碼和底層的VM實現看原理,我相信一步步去做一定會逐漸上手。