synchronized是JVM實現同步互斥的一種方式,被synchronized修飾的代碼塊在代碼編譯後在同步代碼塊開始時插入monitorenter 字節碼指令 ,在同步代碼塊結束和異常處會插入monitorexit指令。JVM會保證每一個monitorenter 都有monitorexit於之匹配,任何一個對象都有一個monitor對象和其關聯,當線程執行到monitorenter 指令的時候會嘗試獲取對象對應monitor的全部權,也就是對象的鎖,在代碼塊結束的或者異常的時候會釋放對象對應monitor的全部權。同步方法使用的是acc_synchronized指令實現的同步。html
synchronized是重入的,當一個線程獲取執行到monitorenter 指令的時候會獲取對象對應的monitor的全部權,即得到鎖,鎖的計數器會+1。當這個線程在次獲取鎖的時候鎖的計數器會再+1。當同步代碼塊結束的時候會將鎖的計數器-1,直到鎖的計數器爲0,則釋放鎖的持有權。JVM以這種方式來實現synchronized的可重入性。java
同步代碼塊使用的是括號內的對象git
同步方法使用的是當前的實例對象程序員
靜態方法使用的當前類的class對象(全局鎖)github
在 Java 6 以前,Monitor 的實現徹底依賴底層操做系統的互斥鎖來實現 ,若是要將一個線程進行阻塞或喚起都須要操做系統的協助,這就須要從用戶態切換到內核態來執行,這種切換代價十分昂貴,很耗處理器時間,現代 JDK 中作了大量的優化。算法
偏向鎖編程
當一個線程訪問同步塊並得到鎖的時候會在對象頭的Mark Word和棧幀中記錄線程的ID,之後這個線程再獲取鎖的時候只須要比較下對象頭中存儲的偏向鎖的ID,不須要進行CAS操做加鎖和解鎖便可得到鎖的使用權。api
若是測試失敗則須要檢查下對像頭Mark Word中的鎖標識是不是偏向鎖,若是是則嘗試使用CAS將對象頭中的偏向鎖指向當前線程,不然使用CAS競爭鎖。數組
輕量級鎖bash
偏向鎖運行在一個線程進入同步塊的狀況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級爲輕量級鎖,輕量級鎖的意圖是在沒有多線程競爭的狀況下,經過CAS操做嘗試將MarkWord更新爲指向LockRecord的指針 。
重量級鎖
虛擬機使用CAS操做嘗試將MarkWord更新爲指向LockRecord的指針,若是更新成功表示線程就擁有該對象的鎖;若是失敗,會檢查MarkWord是否指向當前線程的棧幀,若是是,表示當前線程已經擁有這個鎖;若是不是,說明這個鎖被其餘線程搶佔,此時膨脹爲重量級鎖。
synchronized是悲觀鎖,它的同步策略是不管是否會有線程競爭都會加鎖。CAS操做是一種樂觀鎖的核心算法,在執行CAS操做的時候不會掛起線程,他的核心思想是內存值,舊的預期值,新值。在提交的時候比較內存值和舊的預期值是否相等,不相等經過一個while操做新計算想要修改的新值 (自旋)。
獲取鎖會使該線程對應的本地內存置爲失效,線程直接從主內存獲取共享變量。 鎖的釋放會使該線程將本地內存中的共享變量寫入到住內存。
鎖消除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行消除。鎖消除主要斷定依據來源於逃逸分析的數據支持
鎖粗化,若是虛擬機探測到有這樣一串零碎的操做都對同一個對象加鎖,將會把加鎖同步的範圍擴展到整個操做序列的外部,這樣就只須要加鎖一次就夠了
sychronized是JVM原生的鎖,是經過對象頭設置標記實現的,ReentrantLock是基於Lock接口的實現類,經過AQS框架實現的一個可重入鎖。
CountDownLatch(int count) #構造一個以給定計數CountDownLatch
await() #等待當前的計數器清零
await(long timeout, TimeUnit unit) #等待當前的計數器清零或到達超時時間
countDown() #減小鎖存器的計數,若是計數達到零,釋放全部等待的線程。
複製代碼
CountDownLantch使用案例:併發測試工具
CyclicBarrier(int parties) #構造一個新的CyclicBarrier,攔截線程的數量是parties
CyclicBarrier(int parties, Runnable barrierAction) #構造一個新的CyclicBarrier,攔截線程的數量是parties,到達屏障時優先執行barrierAction
await() #等待
await(long timeout, TimeUnit unit) #帶超時時間等待
reset() #將屏障重置爲初始狀態。
複製代碼
Semaphore(int permits) #建立一個Semaphore與給定數量的許可證和非公平公平設置
Semaphore(int permits, boolean fair) #建立一個 Semaphore與給定數量的許可證和給定的公平設置。
acquire() #獲取許可
release() #釋放許可
複製代碼
exchange(V x)
複製代碼
CountDownLatch 是不能夠重置的,因此沒法重用,CyclicBarrier 沒有這種限制,能夠重用。CountDownLatch 通常用於一個線程等待N個線程執行完以後,再執行某種操做。CyclicBarrier 用於N個線程互相等待都達到某個狀態,再執行。
ReentrantReadWriteLock 在沒有任何讀寫鎖時,才能夠取得寫入鎖,策略是悲觀讀。然而,若是讀取執行狀況不少,寫入不多的狀況下,使用 ReentrantReadWriteLock 可能會使寫入線程遭遇飢餓(Starvation)問題,也就是寫入線程吃吃沒法競爭到鎖定而一直處於等待狀態。StampedLock控制鎖有三種模式(寫,讀,樂觀讀 )一個StampedLock狀態是由版本和模式兩個部分組成,鎖獲取方法返回一個數字做爲票據stamp,它用相應的鎖狀態表示並控制訪問,數字0表示沒有寫鎖被受權訪問,在讀鎖上分爲悲觀鎖和樂觀鎖。若讀的操做不少,寫的操做不多的狀況下,你能夠樂觀地認爲,寫入與讀取同時發生概率不多,所以不悲觀地使用徹底的讀取鎖定 。
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
//下面看看樂觀讀鎖案例
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead(); //得到一個樂觀讀鎖
double currentX = x, currentY = y; //將兩個字段讀入本地局部變量
if (!sl.validate(stamp)) { //檢查發出樂觀讀鎖後同時是否有其餘寫鎖發生?
stamp = sl.readLock(); //若是沒有,咱們再次得到一個讀悲觀鎖
try {
currentX = x; // 將兩個字段讀入本地局部變量
currentY = y; // 將兩個字段讀入本地局部變量
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
//下面是悲觀讀鎖案例
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) { //循環,檢查當前狀態是否符合
long ws = sl.tryConvertToWriteLock(stamp); //將讀鎖轉爲寫鎖
if (ws != 0L) { //這是確認轉爲寫鎖是否成功
stamp = ws; //若是成功 替換票據
x = newX; //進行狀態改變
y = newY; //進行狀態改變
break;
}
else { //若是不能成功轉換爲寫鎖
sl.unlockRead(stamp); //咱們顯式釋放讀鎖
stamp = sl.writeLock(); //顯式直接進行寫鎖 而後再經過循環再試
}
}
} finally {
sl.unlock(stamp); //釋放讀鎖或寫鎖
}
}
}
複製代碼
ReentrantReadWriteLock使用時要注意鎖的升級和降級問題: 讀寫鎖使用
通訊 是指線程之間以何種機制來交換信息。在命令式編程中,線程之間的通訊機制有兩種:共享內存 和 消息傳遞。 在共享內存的併發模型裏,線程之間共享程序的公共狀態,線程之間經過寫-讀內存中的公共狀態來隱式進行通訊。 在消息傳遞的併發模型裏,線程之間沒有公共狀態,線程之間必須經過明確的發送消息來顯式進行通訊。 同步 是指程序用於控制不一樣線程之間操做發生相對順序的機制。 Java 的併發採用的是共享內存模型,Java 線程之間的通訊老是隱式進行,整個通訊過程對程序員徹底透明。
在 Java 中,全部實例域、靜態域 和 數組元素存儲在堆內存中,堆內存在線程之間共享, 局部變量、方法定義參數 和異常處理器參數 不會在線程之間共享,它們不會有內存可見性問題,也不受內存模型的影響。
JMM 定義了線程與主內存之間的抽象關係: 共享變量都存儲在主內存,每條線程還有本身的工做內存,保存了被該線程使用到的變量的主內存副本拷貝。線程對變量的全部操做(讀取、賦值)都必須在工做內存中進行,不能直接讀寫主內存的變量。不一樣的線程之間也沒法直接訪問對方工做內存的變量,線程間變量值的傳遞須要經過主內存。
對一個 volatile 變量的讀,老是能看到(任意線程)對這個 volatile 變量最後的寫入。對一個 volatile 變量的讀會將本地內存的值置爲失效從主內存獲取。對一個 volatile 變量的寫會將本地內存的值寫入主內存。被volatile關鍵字修飾變量會靜止指令重排序優化。被volatile關鍵字修飾的變量只能保證單個變量的原子性相似於 volatile++ 這種複合操做,這些操做總體上不具備原子性。
1)在構造函數內對一個final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操做之間不能重排序。 2)初次讀一個包含final域的對象的引用,與隨後初次讀這個final域,這兩個操做之間不能重排序。
寫final域的重排序規則能夠確保:在對象引用爲任意線程可見以前,對象的final域已經被正確初始化過了,而普通域不具備這個保障。
在 JMM 中,若是一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必需要存在 happens-before 關係。這裏提到的兩個操做既能夠是在一個線程以內,也能夠是在不一樣線程之間。
建立/銷燬線程伴隨着系統開銷,過於頻繁的建立/銷燬線程,會很大程度上影響處理效率 。
線程併發數量過多,搶佔系統資源從而致使阻塞。
對線程進行一些簡單的管理 。
1)AbortPolicy:直接拋出異常。
2) CallerRunsPolicy:只用調用者所在線程來運行任務。
3) DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
4) DiscardPolicy:不處理,丟棄掉
若是當前運行的線程少於corePoolSize,則建立新線程來執行任務(注意,執行這一步驟須要獲取全局鎖)
若是運行的線程等於或多於corePoolSize,則將任務加入BlockingQueue。
若是沒法將任務加入BlockingQueue(隊列已滿),則建立新的線程來處理任務(注意,執行這一步驟須要獲取全局鎖)。
若是建立新線程將使當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法。
execute():ExecutorService.execute 方法接收一個 Runable 實例,它用來執行一個任務。
submit():ExecutorService.submit() 方法返回的是 Future 對象。能夠用 isDone() 來查詢 Future 是否已經完成,當任務完成時,它具備一個結果,能夠調用 get() 來獲取結果。也能夠不用 isDone() 進行檢查就直接調用 get(),在這種狀況下,get() 將阻塞,直至結果準備就緒。
###5.JAVA中默認的線程池有哪些?
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
複製代碼
SingleThreadExecutor線程池只有一個核心線程在工做,也就是至關於單線程串行執行全部任務。若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它。此線程池保證全部任務的執行順序按照任務的提交順序執行。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
複製代碼
FixedThreadPool 是固定大小的線程池,只有核心線程。每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結束,那麼線程池會補充一個新線程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
複製代碼
CachedThreadPool 是無界線程池,若是線程池的大小超過了處理任務所須要的線程,那麼就會回收部分空閒(60 秒不執行任務)線程,當任務數增長時,此線程池又能夠智能的添加新線程來處理任務,適合執行任務時間短的異步任務。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
複製代碼
ScheduledThreadPoolExecutor繼承自ThreadPoolExecutor,用於處理定時任務。
scheduleAtFixedRate:該方法在initialDelay時長後第一次執行任務,之後每隔period時長,再次執行任務。注意,period是從任務開始執行算起的。開始執行任務後,定時器每隔period時長檢查該任務是否完成,若是完成則再次啓動任務,不然等該任務結束後纔再次啓動任務。
scheduleWithFixDelay:該方法在initialDelay時長後第一次執行任務,之後每當任務執行完成後,等待delay時長,再次執行任務。
##併發容器 ###1.ConcurrentHashMap 在多線程環境下,使用HashMap進行put操做時會致使鏈表成環。
ConcurrentHashMap的get
操做是無鎖的,get
操做和put
能夠同時進行,這是ConcurrentHashMap是弱一致性的根本緣由。ConcurrentHashMap的弱一致性