java並發編程實戰--讀後總結
1.簡介
1.1並發簡史
產生原因:資源利用率、公平性、便利性
1.2線程的優勢
1.2.1發揮多處理器的強大功能
1.2.2建模的簡單性
1.2.3異步事件的簡化處理
1.2.4響應更靈敏的用戶界面
1.3線程帶來的風行
1.3.1安全性問題
永遠不發生糟糕的事情
1.3.2活躍性問題
某件正確的事情最終會發生
1.3.3性能問題
希望正確的事情盡快發生
1.4線程無處不在
所使用的框架可能會創建線程
Timer類,用於延遲運行任務一次或多次,確保訪問對象是線程安全的
Servlet和JSP,需要保證多個Servlet共享對象的線程安全
遠程方法調用
Swing和AWT
2.線程安全性
2.1什麽是線程安全性
多個線程訪問某個類時,這個類始終都能表現出正確的行為,這個類是線程安全的
無狀態對象一定是線程安全的
2.2原子性
2.2.1競態條件
當計算的正確性取決於線程的執行時序時,就會發生竟態條件
基於失效的觀察結果做出判斷的竟態條件稱為“先檢查後執行”
2.2.2示例:延遲初始化中的競態條件
2.2.3復合操作
將“先檢查後執行”和“讀取-修改-寫入”等操作統稱為復合操作
2.3加鎖機制
2.3.1內置鎖
2.3.2重入
2.4用鎖來保護狀態
2.5活躍性與性能
當執行時間較長的計算或者可能無法快速完成的操作時,一定不要持有鎖
3.對象的共享
3.1可見性
3.1.1失效數據
獲取到一個未更新的值
3.1.2非原子的64位操作
獲取的失效數據是之前的值而非隨機值是最低安全性,但不適用於非volaties類型的64位數值變量,因為JVM會把64位的讀操作和寫操作分解為2個32位的操作
在多線程中使用共享且可變的long和double等類型的變量是不安全的,除非用volatie聲明或鎖保護
3.1.3加鎖與可見性
3.1.4Volatile變量
Volatile聲明的變Volatile變量量是共享的,讀取Volatile變量總能獲取最新的值
但是Volatile變量比較脆弱,僅當Volatile變量能簡化代碼的失效以及對同步策略的驗證時才應該使用它們
加鎖機制可以確保可見性和原子性,Volatile變量只能確保可見性
3.2發布與逸出
發布:使對象能夠在當前作用域之外的代碼中使用
逸出:不應該被發布的對象被發布時稱為逸出
安全的對象構造過程
不要在構造過程使用this逸出
使用工廠來放在this引用在構造過程中逸出
3.3線程封閉
3.3.1Ad-hoc線程封閉
維護線程封閉性的職責完全由程序實現,但是具有脆弱性,盡量少用
3.3.2棧封閉
3.3.3ThreadLocal類
使線程中的值與保存值的對象關聯起來,總能獲取到最新值
ThreadLocal變量類似於全局變量,降低了代碼的可重用性,在類之間引入了隱含的耦合性
3.4不變性
3.4.1Final域
3.4.2示例:使用Volatile類型來發布不可變對象
3.5安全發布
3.5.1不正確的發布:正確的對象被破壞
3.5.2不口不對象與初始化安全性
3.5.3安全發布的常用模式
在靜態初始化函數中初始化一個對象引用
將對象的引用保存到volatile類型的域或者AtomicReferance對象中
將對象的引用保存到某個正確構造對象的final類型域中
將對象的引用保存到一個由鎖保護的域中
3.5.4事實不可變對象
對象在技術上看是可變,但是其狀態在發布後不會再改變的對象稱為“事實不可變對象”
3.5.5可變對象
不可變對象可以任意發布
事實不可變對象通過安全方式發布
可變對象通過安全方式發布,並且必須是線程安全的或者鎖保護起來
3.5.6安全的共享對象
策略:線程封閉、只讀共享、線程安全共享、保護對象
4.對象的組合
4.1設計線程安全的類
4.1.1收集同步需求
4.1.2依賴狀態的操作
4.1.3狀態的所有權
4.2實例封閉
4.2.1Java監視器模式
4.2.2實例:車輛追蹤
4.3線程安全性的委托
4.3.1示例:基於委托的車輛追蹤器
4.3.2獨立的狀態變量
4.3.3當委托失效時
4.3.4發布底層的狀態變量
4.3.5示例:發布狀態的車輛追蹤器
4.4在現有的線程安全類中添加功能
4.4.1客戶端加鎖機制
對使用某個對象X的客戶端代碼,使用X本身用於保護其狀態的鎖來保護這段客戶端代碼
若沒有則添加的正確的輔助類
public class ListHelper<E> { public List<E> list = Collections.synchronizedList(new ArrayList<>()); public boolean putIfAbsent(E x) { synchronized (list) {//註意加鎖的對象 boolean absent = !list.contains(x); if (absent) { list.add(x); } return absent; } } }
會破壞同步策略的封裝性
4.4.2組合
public class ImprovedList<T> implements List<T> { private final List<T> list; public ImprovedList(List<T> list) { this.list = list; } public synchronized boolean putIfAbsent(T x) { boolean contains = list.contains(x); if (contains) { list.add(x); } return !contains; } }
4.5將同步策略文檔化
解釋含糊的文檔
5.基礎構建模塊
5.1同步容器類
5.1.1同步容器類的問題
5.1.2叠代器與Concurrent-ModificationException
5.1.3隱藏叠代器
5.2並發容器
5.2.1ConcurrentHashMap
5.2.2額外的原子Map操作
5.2.3CopyOnWriteArrayList
當叠代操作遠遠多於修改操作時才應該使用“寫入時復制”容器
5.3阻塞隊列和生產者-消費模式
5.3.1示例:桌面搜索
5.3.2串行線程封閉
5.3.3雙端隊列與工作密取
5.4阻塞方法與中斷方法
恢復中斷
5.5同步工具類
5.5.1閉鎖
延遲線程的進度直到其到達終止狀態,用於確保某些活動直到其他活動都完成才執行
public class TestHarness { public long timeTasks(int nThrads, final Runnable task) throws InterruptedException { final CountDownLatch startGate = new CountDownLatch(1);//起始門 final CountDownLatch endGate = new CountDownLatch(nThrads);//結束門 for (int i = 0; i < nThrads; i++) { Thread t = new Thread() { public void run() { try { startGate.await();//等待所有線程準備就緒 try { task.run(); } finally { endGate.countDown();//完成一件事情就減1 } } catch (InterruptedException ignored) { } } }; t.start(); } long start = System.nanoTime(); startGate.countDown(); endGate.await(); long end = System.nanoTime(); return end - start; } }
5.5.2FutureTask
用於閉鎖
5.5.3信號量
計數信號量用於控制同時訪問特定資源的操作數量
5.5.4柵欄
所有線程都必須同時到達柵欄才能繼續執行,柵欄可以重置復用
5.6構建高效且可伸縮的結果緩存
6.任務執行
6.1在線程中執行任務
6.1.1串行地執行任務
6.1.2顯示地為任務創建線程
6.1.3無限制創建線程的不足
線程生命周期的開銷非常高
資源消耗大
穩定性差
6.2Executor框架
6.2.1示例:基於Executor的Web服務器
public class Test { private static final int NTHREADS = 100; private static final Executor exec = Executors.newFixedThreadPool(NTHREADS); public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(8080); while (true) { final Socket connection = socket.accept(); Runnable task = new Runnable() { public void run() { handleRequest(connection); } }; exec.execute(task); } } }
6.2.2執行策略
6.2.3線程池
newFixedThreadPool()創建固定長度的線程池
newCachedThreadPool()創建可緩存的線程池,當前規模超過處理需求時,回收空閑線程,需求增加時,添加新線程,規模大小無限制
newScheduledThreadPool()以延遲或定時方式執行任務的固定線程池
6.2.4Executor的生命周期
6.2.5延遲任務與周期任務
Timer類精準性有問題,而且拋出異常就會被取消
newScheduledThreadPool是基於相對時間的,用於間隔時間的執行的任務,例如1分鐘檢查一次活動
6.3找出可利用的並行性
6.3.1示例:串行的頁面渲染器
6.3.2攜帶結果的任務Callable與Future
6.3.3示例使用Future實現頁面渲染器
6.3.4在異構任務並行化中存在的局限
6.3.5CompletionService:Executor與BlockingQueue
6.3.6示例:使用CompletionService實現頁面渲染器
6.3.7為任務設置時限
6.3.8示例:旅行預定門戶網站
invokeAll()可以將按照任務集合中叠代器的順序將所有Future添加到返回的集合中,當所有任務執行完畢或中斷或超時,invokeAll將返回
7.取消與關閉
7.1任務取消
7.1.1中斷
用戶請求取消
有時間限制的操作,搜索任務超時,取消正在搜索的任務
應用程序事件,解決方法找到時,取消其他搜索解決方案的任務
錯誤
關閉,程序或服務器關閉
發出中斷請求,線程在合適的時刻中斷自己
7.1.2中斷策略
7.1.3響應中斷
7.1.4示例:計時運行
7.1.5通過Future來實現取消
7.1.6處理不可中斷的阻塞
Java.io包中的同步Socket I/O
Java.io包中的同步I/O
Selector的異步I/O
獲取某個鎖
7.1.7才有newTaskFor來封裝非標準的取消
7.2停止基於線程的服務
7.2.1示例:日誌服務
7.2.2關閉ExecutorService
7.2.3“毒丸”對象
當隊列得到這個“毒丸”對象,立即停止
7.2.4示例:只執行一次的服務
boolean checkMail(Set<String> hosts, long timeout, TimeUnit unit) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); final AtomicBoolean hasNewMail = new AtomicBoolean((false)); try { for (final String host : hosts) { exec.execute(new Runnable() { public void run() { if (checkMail(host)) { hasNewMail.set(true); } } }); } } finally { exec.shutdown();//安全的關閉 exec.awaitTermination(timeout, unit); } return hasNewMail.get(); }
7.2.5shuadownNow的局限性
7.3處理非正常的線程終止
未捕獲一次的處理
7.4JVM關閉
7.4.1關閉鉤子
通過Runtime.addShutdownHook註冊的但尚未開始的線程
用於實現服務或應用程序的清理工作
7.4.2守護線程
主線程創建的所有線程都是普通線程
主線程創建之外的所有線程都是守護線程
7.4.3終結器
8.線程池的使用
8.1在任務與執行處理之間的耦合
8.1.1線程饑餓死鎖
一個線程無期限的等待其他線程的資源或條件會發送線程饑餓死鎖
8.1.2運行時間較長的任務
8.2設置線程池的大小
8.3配置ThreadPoolExecutor
8.3.1線程的創建與銷毀
8.3.2管理隊列任務
8.3.3飽和策略
“中止”策略,拋棄下一個將要被執行的任務
“調用者運行”策略,將任務回退給調用者
8.3.4線程工廠
8.3.5在調用構造函數後再定制ThreadPoolExecutor
8.4擴展ThreadPoolExecutor
示例:給線程池添加統計信息
8.5遞歸算法的並行化
示例:謎題框架
9.圖形用戶界面應用程序
9.1為什麽GUI是多線程的
9.1.1串行事件處理
9.1.2Swing中的線程封閉機制
9.2短時間的GUI任務
9.3長時間的GUI任務
9.3.1取消
9.3.2進度標識和完成標識
9.3.3SwingWorker
9.4共享數據模型
9.4.1線程安全的數據模型
9.4.2分解數據模型
9.5其他形式的單線程子系統
10.避免活躍性危險
10.1死鎖
10.1.1鎖順序死鎖
10.1.2動態的鎖順序死鎖
10.1.3在協作對象之間發送的死鎖
10.1.4開放調用
調用方法時不持有鎖,這種調用稱為開放調用
10.1.5資源死鎖
10.2死鎖的避免與診斷
10.2.1支持定時的鎖
10.2.2通過線程轉儲信息來分析死鎖
10.3其他活躍性危險
10.3.1饑餓
要避免使用線程優先級,會增加平臺依賴性
10.3.2糟糕的響應性
10.3.3活鎖
多個相互協作的線程對彼此進行響應從而修改各自的狀態,使得任何一個線程都無法繼續執行時,就發生了活鎖
通過等待隨機長度的時間和回退可以有效的避免活鎖的發生
11.性能與可伸縮性
11.1對性能的思考
11.1.1性能與可伸縮性
運行速度指標:服務器時間、等待時間
處理能力指標:生產量、吞吐量
可伸縮性:增加計算資源時,程序的吞吐量或者處理能力相應地增加
11.1.2評估各種性能權衡因素
11.2Amdahl定律
11.2.1示例:在各種框架中隱藏的串行部分
11.2.2Amdahl定律的應用
準確估計出執行過程中串行部分所占的比例
11.3線程引入的開銷
11.3.1上下文切換
可運行的線程大於CPU數量時,保存當前運行線程的執行上下文,新調度進來的線程執行上下文設置為當前上下文
11.3.2內存同步
11.3.3阻塞
11.4減少鎖的競爭
11.4.1縮小鎖的範圍("快進快出")
11.4.2減小鎖的粒度
11.4.3鎖分段
11.4.4避免熱點域
11.4.5一些替代獨占鎖的方法
11.4.6監測CPU的利用率
負載不充足
I/O密集
外部限制
鎖競爭
11.4.7向對象池說“不”
11.5示例:比較Map的性能
11.6減少上下文切換的開銷
12.並發程序的測試
12.1正確性測試
12.1.1基本的單元測試
12.1.2對阻塞操作的測試
12.1.3安全性測試
12.1.4資源管理的測試
12.1.5使用回調
12.1.6產生更多的交替操作
12.2性能測試
12.2.1在PutTakeTest增加計時功能
12.2.2多種算法的比較
12.2.3響應性衡量
12.3避免性能測試的陷阱
12.3.1垃圾回收
12.3.2動態編譯
12.3.3對代碼路徑的不真實采樣
12.3.4不真實的競爭程度
12.3.5無用代碼的清除
12.4其他的測試方法
12.4.1代碼審查
12.4.2靜態分析工具
12.4.3面向方面的測試技術
12.4.4分析與監測工具
13.顯示鎖
13.1Lock與ReentrantLock
13.1.1輪詢鎖與定時鎖
13.1.2可中斷的鎖獲取操作
13.1.3非塊結構的加鎖
13.2性能考慮因素
13.3公平性
13.4在synchronized和ReentrantLock之間進行宣傳
13.5讀-寫鎖
14.構建自定義的同步工具
14.1狀態依賴性的管理
14.1.1示例:將前提條件的失敗傳遞給調用者
14.1.2示例:通過輪詢與休眠來實現簡單的阻塞
14.1.3條件隊列
14.2使用條件隊列
14.2.1條件謂詞
14.2.2過早喚醒
14.2.3丟失的信號
14.2.4通知
14.2.5示例:閥門類
14.2.6子類的安全問題
14.2.7封裝條件隊列
14.2.8入口協議與出口協議
14.3顯示的Condition對象
14.4Synchronized剖析
14.5AbstractQueuedSynchronizer
14.6java.util.concurrent同步器類的AQS
14.6.1ReentrantLock
14.6.2Semaphore與CountDownLatch
14.6.3FutureTask
14.6.4ReentrantReadWriteLock
15.原子變量與非阻塞同步機制
15.1鎖的劣勢
15.2硬件對並發的支持
15.2.1比較並交換
15.2.2非阻塞的計數器
15.2.3JVM對CAS的支持
15.3原子變量類
15.3.1原子變量是一種更好的volatile
15.3.2性能比較:鎖與原子變量
15.4非阻塞算法
15.4.1非阻塞的棧
15.4.2非阻塞的鏈表
15.4.3原子的域更新器
15.4.4ABA問題
16.Java內存模型
16.1什麽是內存模型,為什麽需要它
16.1.1平臺的內存模型
16.1.2重排序
16.1.3Java內存模型簡介
16.1.4借助同步
16.2發布
16.2.1不安全的發布
16.2.2安全的發布
16.2.3安全初始化模式
16.2.4雙重檢查加鎖
16.3初始化過程中的安全性
java並發編程實戰--讀後總結