JAVA高并发编程
-
- synchronized 關鍵字
- 同步方法
- 同步代碼塊
- 鎖的底層實現
- 鎖的種類
- volatile 關鍵字
- wait?ify
- AtomicXxx 類型組
- CountDownLatch 門閂
- 鎖的重入
- ReentrantLock
- 同步容器
- Map/Set
- List
- Queue
- ThreadPool&Executor
- Executor
- ExecutorService
- Future
- Callable
- Executors
- FixedThreadPool
- CachedThreadPool
- ScheduledThreadPool
- SingleThreadExceutor
- ForkJoinPool
- WorkStealingPool
- ThreadPoolExecutor
- synchronized 關鍵字
?
synchronized 關鍵字
可用來給對象和方法或者代碼塊加鎖,當它鎖定一個方法或者一個代碼塊的時候,同一時刻最多只有一個線程執行這段代碼。可能鎖對象包括: this, 臨界資源對象,Class 類對象
同步方法
同步方法鎖定的是當前對象。當多線程通過同一個對象引用多次調用當前同步方法時, 需同步執行。
public synchronized void test(){System.out.println("測試一下");}- 1
- 2
- 3
同步代碼塊
同步代碼塊的同步粒度更加細致,是商業開發中推薦的編程方式。可以定位到具體的同步位置,而不是簡單的將方法整體實現同步邏輯。在效率上,相對更高。?
鎖定臨界對象?
同步代碼塊在執行時,是鎖定 object 對象。當多個線程調用同一個方法時,鎖定對象不變的情況下,需同步執行。
- 1
- 2
- 3
- 4
- 5
鎖定當前對象
public void test(){synchronized(this){System.out.println("測試一下");}}- 1
- 2
- 3
- 4
- 5
鎖的底層實現
Java 虛擬機中的同步(Synchronization)基于進入和退出管程(Monitor)對象實現。同步方法 并不是由 monitor enter 和 monitor exit 指令來實現同步的,而是由方法調用指令讀取運行時常量池中方法的 ACC_SYNCHRONIZED 標志來隱式實現的。?
對象頭:存儲對象的 hashCode、鎖信息或分代年齡或 GC 標志,類型指針指向對象的類元數據,JVM 通過這個指針確定該對象是哪個類的實例等信息。?
實例變量:存放類的屬性數據信息,包括父類的屬性信息?
填充數據:由于虛擬機要求對象起始地址必須是 8 字節的整數倍。填充數據不是必須存在的,僅僅是為了字節對齊?
當在對象上加鎖時,數據是記錄在對象頭中。當執行 synchronized 同步方法或同步代碼塊時,會在對象頭中記錄鎖標記,鎖標記指向的是 monitor 對象(也稱為管程或監視器鎖) 的起始地址。每個對象都存在著一個 monitor 與之關聯,對象與其 monitor 之間的關系有存在多種實現方式,如 monitor 可以與對象一起創建銷毀或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有后,它便處于鎖定狀態。?
在 Java 虛擬機(HotSpot)中,monitor 是由 ObjectMonitor 實現的。?
ObjectMonitor 中有兩個隊列,_WaitSet 和 _EntryList,以及_Owner 標記。其中_WaitSet 是用于管理等待隊列(wait)線程的,_EntryList 是用于管理鎖池阻塞線程的,_Owner 標記用于記錄當前執行線程。線程狀態圖如下:
當多線程并發訪問同一個同步代碼時,首先會進入_EntryList,當線程獲取鎖標記后,?
monitor 中的_Owner 記錄此線程,并在 monitor 中的計數器執行遞增計算(+1),代表鎖定,其他線程在_EntryList 中繼續阻塞。若執行線程調用 wait 方法,則 monitor 中的計數器執行賦值為 0 計算,并將_Owner 標記賦值為 null,代表放棄鎖,執行線程進如_WaitSet 中阻塞。若執行線程調用 notify/notifyAll 方法,_WaitSet 中的線程被喚醒,進入_EntryList 中阻塞,等待獲取鎖標記。若執行線程的同步代碼執行結束,同樣會釋放鎖標記,monitor 中的_Owner 標記賦值為 null,且計數器賦值為 0 計算。
鎖的種類
Java 中鎖的種類大致分為偏向鎖,自旋鎖,輕量級鎖,重量級鎖。?
鎖的使用方式為:先提供偏向鎖,如果不滿足的時候,升級為輕量級鎖,再不滿足,升級為重量級鎖。自旋鎖是一個過渡的鎖狀態,不是一種實際的鎖類型。?
鎖只能升級,不能降級。
重量級鎖
在鎖的底層實現中解釋的就是重量級鎖。
偏向鎖
是一種編譯解釋鎖。如果代碼中不可能出現多線程并發爭搶同一個鎖的時候,JVM 編譯代碼,解釋執行的時候,會自動的放棄同步信息。消除 synchronized 的同步代碼結果。使用鎖標記的形式記錄鎖狀態。在 Monitor 中有變量 ACC_SYNCHRONIZED。當變量值使用的時候, 代表偏向鎖鎖定。可以避免鎖的爭搶和鎖池狀態的維護。提高效率。
輕量級鎖?
過渡鎖。當偏向鎖不滿足,也就是有多線程并發訪問,鎖定同一個對象的時候,先提升為輕量級鎖。也是使用標記 ACC_SYNCHRONIZED 標記記錄的。ACC_UNSYNCHRONIZED 標記記錄未獲取到鎖信息的線程。就是只有兩個線程爭搶鎖標記的時候,優先使用輕量級鎖。?
兩個線程也可能出現重量級鎖。
自旋鎖?
是一個過渡鎖,是偏向鎖和輕量級鎖的過渡。?
當獲取鎖的過程中,未獲取到。為了提高效率,JVM 自動執行若干次空循環,再次申請鎖,而不是進入阻塞狀態的情況。稱為自旋鎖。自旋鎖提高效率就是避免線程狀態的變更。
volatile 關鍵字
變量的線程可見性。在 CPU 計算過程中,會將計算過程需要的數據加載到 CPU 計算緩存中,當 CPU 計算中斷時,有可能刷新緩存,重新讀取內存中的數據。在線程運行的過程中,如果某變量被其他線程修改,可能造成數據不一致的情況,從而導致結果錯誤。而 volatile 修飾的變量是線程可見的,當 JVM 解釋 volatile 修飾的變量時,會通知 CPU,在計算過程中, 每次使用變量參與計算時,都會檢查內存中的數據是否發生變化,而不是一直使用 CPU 緩存中的數據,可以保證計算結果的正確。?
volatile 只是通知底層計算時,CPU 檢查內存數據,而不是讓一個變量在多個線程中同步。
- 1
wait?ify
AtomicXxx 類型組
原子類型。?
在 concurrent.atomic 包中定義了若干原子類型,這些類型中的每個方法都是保證了原子操作的。多線程并發訪問原子類型對象中的方法,不會出現數據錯誤。在多線程開發中,如果某數據需要多個線程同時操作,且要求計算原子性,可以考慮使用原子類型對象。
- 1
- 2
- 3
- 4
注意:原子類型中的方法是保證了原子操作,但多個方法之間是沒有原子性的。如:
AtomicInteger i = new AtomicInteger(0); if(i.get() != 5){i.incrementAndGet(); }- 1
- 2
- 3
- 4
在上述代碼中,get 方法和 incrementAndGet 方法都是原子操作,但復合使用時,無法保證原子性,仍舊可能出現數據錯誤。
CountDownLatch 門閂
門閂是 concurrent 包中定義的一個類型,是用于多線程通訊的一個輔助類型。?
門閂相當于在一個門上加多個鎖,當線程調用 await 方法時,會檢查門閂數量,如果門
閂數量大于 0,線程會阻塞等待。當線程調用 countDown 時,會遞減門閂的數量,當門閂數量為 0 時,await 阻塞線程可執行。
CountDownLatch latch = new CountDownLatch(5);void m1(){try {latch.await();// 等待門閂開放。} catch (InterruptedException e) {e.printStackTrace();}System.out.println("m1() method");}void m2(){for(int i = 0; i < 10; i++){if(latch.getCount() != 0){System.out.println("latch count : " + latch.getCount());latch.countDown(); // 減門閂上的鎖。}try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("m2() method : " + i);}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
鎖的重入
在 Java 中,同步鎖是可以重入的。只有同一線程調用同步方法或執行同步代碼塊,對同一個對象加鎖時才可重入。?
當線程持有鎖時,會在 monitor 的計數器中執行遞增計算,若當前線程調用其他同步代碼,且同步代碼的鎖對象相同時,monitor 中的計數器繼續遞增。每個同步代碼執行結束,?
monitor 中的計數器都會遞減,直至所有同步代碼執行結束,monitor 中的計數器為 0 時,釋放鎖標記,_Owner 標記賦值為 null。
ReentrantLock
重入鎖,建議應用的同步方式。相對效率比 synchronized 高。量級較輕。?
synchronized 在 JDK1.5 版本開始,嘗試優化。到 JDK1.7 版本后,優化效率已經非常好了。在絕對效率上,不比 reentrantLock 差多少。?
使用重入鎖,必須必須必須手工釋放鎖標記。一般都是在 finally 代碼塊中定義釋放鎖標記的 unlock 方法。
公平鎖
private static ReentrantLock lock = new ReentrantLock(true);public void run(){for(int i = 0; i < 5; i++){lock.lock();try{System.out.println(Thread.currentThread().getName() + " get lock");}finally{lock.unlock();}}}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
8ThreadLocal
remove 問題?
同步容器
解決并發情況下的容器線程安全問題的。給多線程環境準備一個線程安全的容器對象。線程安全的容器對象: Vector, Hashtable。線程安全容器對象,都是使用 synchronized?
方法實現的。?
concurrent 包中的同步容器,大多數是使用系統底層技術實現的線程安全。類似 native。?
Java8 中使用 CAS。
Map/Set
ConcurrentHashMap/ConcurrentHashSet
底層哈希實現的同步 Map(Set)。效率高,線程安全。使用系統底層技術實現線程安全。量級較 synchronized 低。key 和 value 不能為 null。
ConcurrentSkipListMap/ConcurrentSkipListSet
底層跳表(SkipList)實現的同步 Map(Set)。有序,效率比 ConcurrentHashMap 稍低。
List
CopyOnWriteArrayList
寫時復制集合。寫入效率低,讀取效率高。每次寫入數據,都會創建一個新的底層數組。
Queue
ConcurrentLinkedQueue
基礎鏈表同步隊列。
LinkedBlockingQueue?
阻塞隊列,隊列容量不足自動阻塞,隊列容量為 0 自動阻塞。
ArrayBlockingQueue?
底層數組實現的有界隊列。自動阻塞。根據調用 API(add/put/offer)不同,有不同特性。?
當容量不足的時候,有阻塞能力。?
add 方法在容量不足的時候,拋出異常。?
put 方法在容量不足的時候,阻塞等待。?
offer 方法,?
單參數 offer 方法,不阻塞。容量不足的時候,返回 false。當前新增數據操作放棄。三參數 offer 方法(offer(value,times,timeunit)),容量不足的時候,阻塞 times 時長(單?
位為 timeunit),如果在阻塞時長內,有容量空閑,新增數據返回 true。如果阻塞時長范圍?
內,無容量空閑,放棄新增數據,返回 false。
DelayQueue?
延時隊列。根據比較機制,實現自定義處理順序的隊列。常用于定時任務。如:定時關機。
LinkedTransferQueue?
轉移隊列,使用 transfer 方法,實現數據的即時處理。沒有消費者,就阻塞。
SynchronusQueue?
同步隊列,是一個容量為 0 的隊列。是一個特殊的 TransferQueue。必須現有消費線程等待,才能使用的隊列。?
add 方法,無阻塞。若沒有消費線程阻塞等待數據,則拋出異常。?
put 方法,有阻塞。若沒有消費線程阻塞等待數據,則阻塞。
ThreadPool&Executor
Executor
線程池頂級接口。?
常用方法 - void execute(Runnable)?
作用是: 啟動線程任務的。
ExecutorService
Executor 接口的子接口。?
常見方法 - Future submit(Callable), Future submit(Runnable)
Future
未來結果,代表線程任務執行結束后的結果。
Callable
可執行接口。?
接口方法 : Object call();相當于 Runnable 接口中的 run 方法。區別為此方法有返回值。不能拋出已檢查異常。?
和 Runnable 接口的選擇 - 需要返回值或需要拋出異常時,使用 Callable,其他情況可任意選擇。
Executors
工具類型。為 Executor 線程池提供工具方法。類似 Arrays,Collections 等工具類型的功用。
FixedThreadPool
容量固定的線程池?
queued tasks - 任務隊列?
completed tasks - 結束任務隊列
CachedThreadPool
緩存的線程池。容量不限(Integer.MAX_VALUE)。自動擴容。默認線程空閑 60 秒,自動銷毀。
ScheduledThreadPool
計劃任務線程池。可以根據計劃自動執行任務的線程池。
SingleThreadExceutor
單一容量的線程池。
ForkJoinPool
分支合并線程池(mapduce 類似的設計思想)。適合用于處理復雜任務。初始化線程容量與 CPU 核心數相關。?
線程池中運行的內容必須是 ForkJoinTask 的子類型(RecursiveTask,RecursiveAction)。
WorkStealingPool
JDK1.8 新增的線程池。工作竊取線程池。當線程池中有空閑連接時,自動到等待隊列中竊取未完成任務,自動執行。?
初始化線程容量與 CPU 核心數相關。此線程池中維護的是精靈線程。?
ExecutorService.newWorkStealingPool();
ThreadPoolExecutor
線程池底層實現。除 ForkJoinPool 外,其他常用線程池底層都是使用 ThreadPoolExecutor?
實現的。?
public ThreadPoolExecutor?
(int corePoolSize, // 核心容量
int maximumPoolSize, // 最大容量?
long keepAliveTime, // 生命周期,0 為永久?
TimeUnit unit, // 生命周期單位?
BlockingQueue workQueue // 任務隊列,阻塞隊列。?
);
總結
- 上一篇: 从DWG导入SKP后的封面问题
- 下一篇: UI交互设计教程分享:提高界面交互体验的