聲明:原創作品,轉載時請注明文章來自SAP師太技術博客( 博/客/園www.cnblogs.com):m.survivalescaperooms.com/jiangzhengjun,并以超鏈接形式標明文章原始出處,否則將追究法律責任!原文鏈接:http://m.survivalescaperooms.com/jiangzhengjun/p/4289498.html共享對象... 4
可見性... 4
過期數據... 5
非原子性的64位操作... 5
鎖和可見性... 5
volatile變量... 6
發布與逸出... 7
安全構建的實踐... 8
不可變性... 9
final域... 10
示例:使用volatile發布不可變的對象... 10
安全發布... 12
不可變對象的初始化是安全的... 12
可變對象的安全發布... 13
高效不可變對象... 13
對象(不可變/可變/高效)的發布約束... 14
安全共享對象... 14
向已有的線程安全類添加功能(另附第4章)... 14
重排序(reordering)... 16
happens-before法則... 16
發布... 18
不安全的發布... 18
安全發布... 18
安全初始化技巧... 19
安全初始化(安全構造)... 19
構建塊(Building Blocks)... 20
同步容器... 20
同步容器的問題... 20
迭代器和ConcurrentModificationException. 21
隱藏迭代器... 22
并發容器... 22
ConcurrentHashMap. 25
Map附加的原子操作... 25
CopyOnWriteArrayList25
阻塞隊列和生產者—消費者模式... 26
示例:LinkedBlockingQueue. 29
示例:PRiorityBlockingQueue. 30
示例:DelayQueue. 31
阻塞和可中斷的方法... 33
Synchronizer(同步器)... 33
CountDownLatch(倒計時閉鎖)... 35
CyclicBarrier(障柵)... 37
FutureTask. 39
Semaphore(信號量)... 40
Exchanger(交換器)... 41
SynchronousQueue(同步隊列)... 44
為計算結建立高效可伸縮的調整緩存... 46
使用Memoizer為因式分解的servlet緩存結果:... 49
任務執行(Task Execution)... 50
在線程中執行任務... 50
順序地執行任務... 50
顯式地為任務創建線程... 50
無限制地創建線程的缺點... 51
Executor框架(任務與執行分開)... 51
示例:使用Executor實現Web Server52
執行策略... 53
線程池... 53
Executor的生命周期... 54
定時周期性任務... 55
找出可開發的并行性... 56
示例:順序執行的頁面渲染器... 56
可攜帶結果的任務:Callable和Future. 57
示例:使用Future實現頁面渲染器... 58
CompletionService:Executor 與 BlockingQueue的結合體... 59
示例:使用CompletionService的頁面渲染器... 60
為任務設置時限... 61
方法小結... 62
Future的get、cancel、isCancelled、isDone方法... 62
ExecutorService的submit、invokeAll、invokeAny方法... 62
取消和關閉... 63
取消任務... 63
中斷... 64
中斷策略... 65
響應中斷... 65
示例:限時運行... 66
通過Future取消任務... 69
處理不可中斷阻塞方法... 70
用newTaskFor封閉非標準取消技術... 71
停止基于線程的服務... 72
示例:日志服務... 72
關閉ExecutorService. 75
終結標示對象... 77
示例:一次性服務的關閉... 79
shutdownNow的局限性... 79
處理導致線程終止的異常... 82
處理線程未捕獲的異常... 83
關閉JVM.. 85
關閉鉤子... 85
守護線程... 86
Finalizer86
線程池應用... 87
任務與執行策略的隱式耦合... 87
定制線程池的大小... 88
配置ThreadPoolExecutor89
線程的創建與銷毀... 89
配置任務隊列... 90
飽和策略... 91
線程工廠... 92
ThreadPoolExecutor構造后再配置... 96
擴展ThreadPoolExecutor96
示例:通過擴展線程提供日志和統計功能... 97
并行遞歸... 98
性能和可伸縮性... 99
測試并發程序... 100
正確性測試... 100
基本的單元測試... 101
測試阻塞操作... 101
線程安全性測試... 102
測試資源管理... 104
使用回調... 104
讓線程產生更多的交叉操作... 105
性能測試... 105
擴展PutTakeTest,加入時間特性... 105
比較多種算法... 108
顯式鎖... 108
Lock和ReentrantLock. 108
立即鎖、超時鎖、中斷鎖、公平鎖... 109
在synchronized和ReentrantLock的選擇... 111
讀-寫鎖... 112
構建自己的同步工具... 114
自己構造循環隊列... 114
將驗證失敗信息傳給調用者... 115
利用“輪詢加休眠”實現拙劣的阻塞... 115
讓條件隊列來解決這一切... 116
閥門(可并可關)... 117
顯示的Condition對象... 118
剖析Synchronizer119
AbstractQueuedSynchronizer121
不可重入互斥鎖... 122
一個簡單的閉鎖... 123
原子變量與非阻塞同步機制... 124
鎖的劣勢... 124
硬件對并發的支持... 124
比較并交換... 125
應用模擬的CAS實現非阻塞計數器... 126
JVM對CAS的支持... 127
原子變量... 127
原子變量是“更佳的volatile”. 127
非阻塞算法... 128
非阻塞棧... 129
非阻塞鏈表... 130
Atomic Field Updaters132
共享對象synchronized不僅僅只有原子性,還具有內存可見性。我們不僅希望能夠避免一個線程修改其他線程正在使用的對象的狀態,而且希望確保當一個線程修改了對象的狀態后,其他線程能夠真正看到改變。你可以使用顯示的同步或者利用內置于類庫中的同步機制,來保證對象的安全性。
可見性在多線程環境下,下面程序中當ready為true時,number不一定為42,這是因為它沒有使用恰當的同步機制,沒有保證主線程寫入ready和number的值對讀線程是可見的。
public class NoVisibility {private static boolean ready;
private static int number;
private static class ReaderThread extends Thread { public void run() {while (!ready)
Thread.yield();
//這里可能輸出0,也可能永遠都不會輸出
System.out.println(number);
}
}
public static void main(String[] args) {new ReaderThread().start();
number = 42;
ready = true;
}
}
這里可能會產生兩個問題,一是程序可能一直保持循環,因為對于讀線程來說,ready的值可能永遠不可見。二是輸入的number為0,這是因為重排序引起的,在寫線程將ready與number從工作內存中寫回到主內存中時,在沒有同步的機制下,先寫ready還是先寫number這是不確定的,也就是說將它們寫回到主內存時的順序可能與程序邏輯順序恰好相反,這是因為在單個線程下,只要重排序不會對結果產生影響,這是允許的。
在沒有使用同步的情況下,編譯器、運行器、運行時安排順序可能完全出乎意料,在沒有進行適當同步的多線程程序中,嘗試推斷那些“必然”發生在內存中的動作時,你總是會判斷錯誤。
過期數據上面NoVisibility程序在多線程環境下還可能讀取到過期數據,比如當ready為true時,寫線程已將number域的值置為了42,但在它還未來得及將這個新值從工作內存中寫回到主內存前,讀線程就已將ready從主內存中讀取出來了,這時的值還是為初始的默認值0,這個值顯然是一個已過期了的值,因為number現在真真的值應該為42,而不是0。
在沒有同步的情況下讀取數據類似于數據庫中使用READ_UNCOMMITTED(未提交讀)隔離級別,這時你更愿意用準確性來交換性能。
在NoVisibility中,過期數據可能導致它打印錯誤數值,或者程序無法終止。過期數據可能會使對象引用中的數據更加復雜,比如鏈指針在鏈表中的實現。過期數據還可能引發嚴重且混亂的錯誤,比如意外的異常,臟的數據結構,錯誤的計算和無限的循環。
下面的程序更對過期數據尤為敏感:如果一個線程調用了set,但還未來得及將這個新值寫回到主內存中時,而另一個線程此時正在調用get,它就可能看不到更新的數據了:
@NotThreadSafe
public class MutableInteger {private int value;
public int get() { return value; } public void set(int value) { this.value = value; }}
我們可以將set與get同步,使之成為線程安全的。注,僅僅同步某個方法是沒有用的。
非原子性的64位操作當一個線程在沒有同步的情況下讀取變量,它可能會得到一個過期值。但是至少它可以看到某個線程在那里設定的一個完整而真實數值,而不是一個憑空而來的值。這樣的安全保證被稱為是最低限的安全性。
最低限的安全性應用于所有變量,除了一個例外:沒有聲明為volatile的64位數值變量double和long。Java內存模型規定獲取(read動作)和存儲(write動作)操作都是原子性的,但是對于非volatile的long和double變量,JVM允許將64位的讀回寫劃分為兩個32的操作。如果讀和寫發生在不同的線程,這種情況讀取一個非volatile類型long就可能會現得到一個線程寫的值的高32位和另一個線程寫的值的低32位,最終這個long變量的值由這兩個線程高低位組合而成的值。因此,即使你并不關心過期數據,但僅僅在多線程程序中使用共享的、可變的long和double變量也可能是不安全的,除非將它們聲明為volatile類型,或者鎖保護起來。
鎖和可見性內置鎖可以用來確保一個線程以某種可預見的方法看到另一個線程的影響,像下圖一樣。當B執行到與A相同的鎖監視的同步塊時,A在同步塊之中所做的每件事,對B都是可見的,如果沒有同步,就沒有這樣的保證。

鎖不僅僅是關于同步與互斥的,也是關于內存可見的。為了保證所有線程都能夠看到共享的、可變變的最新值,讀取和寫入線程必須使用公共鎖進行同步。
volatile變量volatile是一種弱同步的形式,它確保對一個變量的更新后對其他線程是可見的。當一個域聲明為volatile類型后,編譯器與運行時會監視這個變量:它是共享的,而且對它的操作不會與其他的內存操作一起被重排序。volatile變量不會緩存在寄存器或者緩存其他處理器隱藏的地方,所以,讀一個volatile類型的變量時,總會返回由某一線程所寫入的最新值。
讀取volatile變量的操作不會加鎖,也就不會引起執行線程的阻塞,這使得volatile變量相對于sychronized而言,只是輕量級的同步機制
volatile變量對可見性的影響所產生的價值遠遠高于變量本身。線程A向volatile變量寫入值,隨后線程B讀取該變量,所有A執行寫操作前可見的變量的值,在B讀取了這個vola
新聞熱點
疑難解答