當做個線程訪問某個類時,這個類始終都能表現出真確的行為,那么就稱這個類是線程安全的。
???線程安全類可以認為是一個在并發環境和單線程環境中都不會被破壞的類。
- 無狀態的對象一定是線程安全的。
- 原子性
- 競態條件:在并發編程中,由于不恰當的執行時序而出現的不正確的結果是一種非常重要的情況,這種情況通常稱為競態條件。【當某個計算的正確性取決于多個線程的交替執行時序時,那么就會發生競態條件】常見的就是:先檢查后執行
- 數據競爭:很容易和競態條件混淆。如果訪問共享的飛final類型的域時,沒有采用同步機制來進行協同,那么就會出現數據競爭。
- 原子操作:對于訪問同一個狀態的所有操作(包括該操作本身)來說,這個操作是一個以原子方式執行的操作。
- 應當盡可能的使用現有的線程安全對象(例如:AtomicLong)來管理類的狀態。
@ThreadSafe
public class CountingFactorizer extends GenericServlet implements Servlet {
private final AtomicLong count = new AtomicLong(0);
public long getCount() { return count.get(); }
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
count.incrementAndGet();
encodeIntoResponse(resp, factors);
}
void encodeIntoResponse(ServletResponse res, BigInteger[] factors) {}
BigInteger extractFromRequest(ServletRequest req) {return null; }
BigInteger[] factor(BigInteger i) { return null; }
}
- 要保持狀態的一致性,就需要在單個原子操作中更新所有相關的狀態變量。
- 內置鎖【Intrinsic Lock或者稱為監視鎖[Monitor Lock]】:java提供了一種內置的鎖機制來支持原子性:同步代碼塊(Synchronized Block)
- 重入:重入進一步提升了加鎖行為的封裝性,因此簡化了面向對象并發代碼的開發。內置所是可以重入的,因此如果某個線程試圖獲取一個已經由它自己只有的鎖,那么這個請求就會成功。意味著獲取鎖的操作的粒度是“線程”,而不是“調用”
如果內置所不是可以重入的,那么下面這段代碼將發生死鎖:
public class Widget{
public synchronized void doSomething(){
....
}
}
public class LoggingWidget extends Widget{
public synchronized void doSomething(){
System.out.println(toString() +": calling doSomething");
super.doSomething();
}
}
- 用鎖來保護狀態:對于可能被多個線程同時訪問的可變狀態變量,在訪問它時都需要持有同一個鎖,在這種情況下,我們稱狀態變量是由這個鎖保護的。
- 每個共享的和可變的變量都應該只由一個鎖來保護,從而使維護人員知道是哪一個鎖。
- 活躍性和性能:活躍性沒有明確的定義。安全性的含義是“永遠不發生糟糕的事情”,而活躍性則關注于另一個目標,即“某件正確的事情最終會發生”。當某個操作無法繼續執行下去時,就會發生活躍性問題。在串行程序中,活躍性問題的形式之一就是無意中造成的無限循環,從而使循環之后的代碼無法得到執行,性能問題在過多加鎖以及沒有正確加鎖的情況下會導致性能問題。
- 不良并發【poor Concurrency】:對servlet加鎖,會導致用戶請求排隊等待處理。