線程安全相關問題
- 如何定義一個對象是否是線程安全的
當多個線程訪問同一個對象時,如果不用考慮這些線程在運行時環(huán)境下的調(diào)度和交替運行,也不需要進行額外的同步,或者在調(diào)用方進行任何其他的協(xié)調(diào)操作贼穆,調(diào)用這個對象的行為都可以獲取正確的結(jié)果,那這個對象是線程安全的
- 線程安全問題的主要原因是
主內(nèi)存和工作內(nèi)存數(shù)據(jù)不一致杆查,重排序(為了性能優(yōu)化扮惦,一般包括編譯器指令重排序和處理器指令重排序)導致競態(tài)條件下的程序執(zhí)行不確定性
synchronized
- synchronized的應用方式主要有修飾實例方法,修飾靜態(tài)方法亲桦,修飾代碼塊
- 修飾實例方法
對實例方法的修飾崖蜜,同步鎖加在當前實例對象上,當一個線程訪問改實例的synchronized方法時客峭,其他線程不能訪問該方法
同步鎖和實例對象一一對應豫领,如果是一個線程 A 需要訪問實例對象 obj1 的 synchronized 方法 f1(當前對象鎖是obj1),另一個線程 B 需要訪問實例對象 obj2 的 synchronized 方法 f2(當前對象鎖是obj2)舔琅,是可以同時訪問同一個方法的等恐,因為兩個實例對象鎖并不同相同
- 修飾靜態(tài)方法
對靜態(tài)方法的修飾,同步鎖加在當前類的class對象上(并非實例對象),所以不同的線程可以同時訪問同一實例的同步靜態(tài)方法和同步實例方法
- 修飾代碼塊
實例對象鎖:synchronized(this){...}
class對象鎖:synchronized(XXX.class){...}
synchronized的可重入性
在一個線程調(diào)用synchronized方法的同時在其方法體內(nèi)部調(diào)用該對象另一個synchronized方法课蔬,也就是說一個線程得到一個對象鎖后再次請求該對象鎖囱稽,是允許的,這就是synchronized的可重入性synchronized與等待喚醒機制(wait, notify)
調(diào)用等待喚醒機制相關方法時二跋,必須獲得當前對象的monitor對象战惊,而只有synchronized關鍵字才能獲取到monitor對象,所以只有在同步方法塊中才能調(diào)用wait, notify方法
synchronized (obj) {
obj.wait();
obj.notify();
obj.notifyAll();
}
wait和sleep的對比:sleep方法并不會讓當前線程釋放鎖扎即,但是wait會命令當前線程在執(zhí)行完同步代碼段后釋放持有的鎖
- Java對象在內(nèi)存中的布局
名稱 | 內(nèi)容 |
---|---|
對象頭 | 具體如下 |
實例變量 | 實例所屬類的屬性吞获,數(shù)組長度等信息 |
填充數(shù)據(jù) | 保持字節(jié)對齊而填充的數(shù)據(jù)(因?qū)ο蟮钠鹗嫉刂繁仨毷?字節(jié)整數(shù)倍) |
- Java對象頭
JVM使用2個字節(jié)來存儲該信息,如果是數(shù)組對象谚鄙,會有1個額外的字節(jié)存儲數(shù)組長度
名稱 | 內(nèi)容 |
---|---|
Mark Word | 存儲對象的hashCode各拷、鎖信息或分代年齡或GC標志等信息 |
Class Metadata Address | 類型指針指向?qū)ο蟮念愒獢?shù)據(jù),JVM通過這個指針確定該對象是哪個類的實例 |
Tips
- 線程隨機競爭同一資源時闷营,如果對訪問順序敏感烤黍,那么此時存在靜態(tài)條件