java并发编程一:基础知识
一、線程安全性
1.1 什么是線程安全性?
當多個線程訪問某個類時,不管運行時環境采用何種調度方式或者這些線程將如何交替執行,并且在主調代碼中不需要任何額外的同步或者協同,這個類都能表現出正確行為。
1.2 原子性
i++ -> "讀取-修改-寫入"
1.2.1 競態條件 和 復合操作
當某個計算的正確性取決于多個線程的交替執行時序時,那么就會發生競態條件?;蛘哒f,之所以發生競態條件是因為操作是非原子性而是一個復合操作。
public class A {private static A instance = null;public static A getInstance(){if(instance == null){instance = new A();}return instance;} }復制代碼這里會存在競態條件(先檢查后執行),假設線程A1和A2同時執行getInstance方法,A1看到instance實例為空,它會去new A();A2也要判斷instance是否為空,此時的instance是否為空取決于不可預測的時序(包括線程調度方式),以及A1要花多長時間new A 實例;如果A1在new操作時,輪到A2線程被調度,那么此時A2判斷instance也為空,最終會出現兩個A實例。 同理i++也存在這樣的競態條件(讀取-修改-寫入) 解決:避免某個線程修改變量時,通過某種方式防止其他線程使用這個變量,即保證操作是原子方式執行。
先檢查后執行 -- 加鎖實現同步
i++ -- concurrent.atomic 實現原子操作
1.3 加鎖機制
1.3.1 內置鎖
Synchronized
鎖重入
“重入”意味著獲取鎖的操作粒度是“線程”,而不是“調用”,避免死鎖。
二、共享對象
2.1 可見性
當讀操作和寫操作在不同的線程中執行時,無法確保執行讀操作的線程能看到其它線程寫入的值。
問題:
在沒有同步的情況下get和set訪問value,數據失效很容易出現:如果某個線程調用了set,那么另一個在調用get的線程可能會看到更新后的value值,也可能看不到。
非volatile類型的64位數值變量long和double,JVM允許將64位的讀操作或者寫操作分解為兩個32位的操作,當讀取一個非volatile類型的long變量時,如果對該變量的讀操作和寫操作在不同的線程中執行,那么很可能讀到某個值得高32位和另一個值得低32位,造成數據失效。
解決:
加鎖不僅僅具有互斥性和包括可見性,為了確保所有線程都能看到共享變量的最新值,所有執行讀操作或者寫操作的線程都必須在同一個鎖上同步。
可見性與重排序 使用場景:確保自身狀態可見,確保引用對象狀態可見,標記重要程序生命周期事件發生(初始化和關閉) 某操作完成、中斷或者狀態標志
2.2 發布與逸出
1. 發布: 發布一個對象的意思是,使對象能夠在當前作用域之外的代碼中使用。
當發布某個對象時,可能間接地發布其他對象。例如如果將一個Secret對象添加到集合secrets中,那么在發布secrets的同時,也會發布Secret對象,因為任何代碼都可以遍歷這個集合,并獲得對Secret對象的引用。
2. 逸出: 當某個不應該發布的對象被發布時,這種情況就是逸出。
對象逸出會導致對象的內部狀態被暴露,可能危及到封裝性,使程序難以維持穩定;若發布尚未構造完成的對象,可能危及線程安全問題。
最常見的逸出是this引用在構造時逸出,導致this引用逸出的常見錯誤有:
當對象在構造函數中顯式還是隱式創建線程時,this引用幾乎總是被新線程共享,于是新的線程在所屬對象完成構造之前就能看見它。 避免構造函數中啟動線程引起的this引用逸出的方法是不要在構造函數中啟動新線程,取而代之的是在其他初始化或啟動方法中啟動對象擁有的線程。
在構造方法中調用那些既不是private也不是final的可被子類覆蓋的實例方法時,同樣導致this引用逸出。 避免此類錯誤的方法時千萬不要在父類構造方法中調用被子類覆蓋的方法。
在構造方法中創建內部類實例時,內部類的實例包含了對封裝實例的隱含引用(深入理解 內部類),可能導致隱式this逸出。例子如下:
不要在構造函數中使用this引用逸出,也不要在構造方法中調用可改寫“實例”的方法;
public class SafeListener { private final EventListener listener; private SafeListener(){ listener = new EventListener(){ public void onEvent(Event e){ doSomething(e); } ); } public static SafeListener newInstance(EventSource source) { SafeListener safe = new SafeListener(); source.registerListener(safe.listener); return safe; } } 復制代碼2.3 線程封閉
當訪問共享的可變數據時,通常需要同步。一種避免使用同步的方式就是不同享數據,這叫做線程封閉。java提供了一些機制來維持線程封閉性,例如局部變量和ThreadLocal類。 線程封閉技術的一個常見應用是JDBC的Connection對象。JDBC規范不要求Connection對象時線程安全的,而要求連接池是線程安全的。線程通過線程池中獲得一個Connection對象,并且用該對象來處理請求,使用完之后再返回給連接池。由于大多數請求(例如Servlet請求和EJB)都是單個線程采用同步的方式來處理,并且在Connection對象返回前,連接池不會再把它分配給其它線程,因此這種連接在處理請求時,把Connection對象封閉在線程中。
2.3.1 棧封閉
將變量封閉在方法中
2.3.2 threadLocal 為每個使用變量的線程都存有一個副本
使用場景:
2.4 不變性
如果一個對象在創建后其狀態就不能被修改,那么這個對象就稱為不可變對象。 不可變對象需要滿足下面條件:
不可變的對象一定是線程安全的
2.5 安全發布
轉載于:https://juejin.im/post/5c348d39f265da616f7024b2
總結
以上是生活随笔為你收集整理的java并发编程一:基础知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring 事务提交回滚源码解析
- 下一篇: 女子驾奇瑞小蚂蚁 高速上油门到底车速为0