線程安全編碼的核心,就是管理對狀態(state)的訪問,尤爲是對(共享shared、可變mutable)狀態的訪問。java
一般,一個對象Object的狀態state就是他的數據data,存儲於狀態變量(state variables)如實例對象或者靜態變量,以及他所依賴的其餘對象。安全
Java中最經常使用的同步機制是使用Synchronized關鍵字,其餘還有volatile變量, explicit locks(顯式鎖), 和atomic variables(原子變量)。網絡
1 public class Date { 2 int /*@spec_public@*/ day; 3 int /*@spec_public@*/ hour; 4 5 /*@invariant 1 <= day && day <= 31; @*/ //class invariant 6 /*@invariant 0 <= hour && hour < 24; @*/ //class invariant 7 8 /*@ 9 @requires 1 <= d && d <= 31; 10 @requires 0 <= h && h < 24; 11 @*/ 12 public Date(int d, int h) { // constructor 13 day = d; 14 hour = h; 15 } 16 17 /*@ 18 @requires 1 <= d && d <= 31; 19 @ensures day == d; 20 @*/ 21 public void setDay(int d) { 22 day = d; 23 } 24 25 /*@ 26 @requires 0 <= h && h < 24; 27 @ensures hour == h; 28 @*/ 29 public void setHour(int h) { 30 hour = h; 31 } 32 }
線程安全的核心概念是:正確性。一個類是否正確,取決於它是否遵照他的規範(specification),一個好的規範,定義了以下兩點內容:多線程
一個無狀態的Servlet必然是線程安全的,以下:less
1 @ThreadSafe 2 public class StatelessFactorizer implements Servlet { 3 public void service(ServletRequest req, ServletResponse resp) { 4 BigInteger i = extractFromRequest(req); 5 BigInteger[] factors = factor(i); 6 encodeIntoResponse(resp, factors); 7 } 8 }
加入一個狀態後,就再也不線程安全了。jvm
1 @NotThreadSafe 2 public class UnsafeCountingFactorizer implements Servlet { 3 private long count = 0; 4 5 public long getCount() { 6 return count; 7 } 8 9 public void service(ServletRequest req, ServletResponse resp) { 10 BigInteger i = extractFromRequest(req); 11 BigInteger[] factors = factor(i); 12 ++count;// 非原子操做 13 encodeIntoResponse(resp, factors); 14 } 15 }
++ 操做符並不是原子操做,它包含三步:讀值,加一,寫入(read-modify-write)ide
多線程中,有可能出現因爲不恰當的執行時序而形成不正確結果的狀況,稱爲競態條件。函數
競態條件一:read-modify-write(先讀取再修改寫入)post
最後的結果依賴於它以前的狀態值,如上++操做性能
競態條件二:check-then-act(先檢查後執行)
示例:lazy initialization
1 @NotThreadSafe 2 public class LazyInitRace { 3 private ExpensiveObject instance = null; 4 5 public ExpensiveObject getInstance() { 6 if (instance == null)// check then act 7 instance = new ExpensiveObject(); 8 return instance; 9 } 10 }
避免競態條件的問題,就須要以「原子」方式執行上述操做,稱之爲「複合操做」。
解決read-modify-write這一類競態條件問題時,一般使用已有的線程安全對象來管理類的狀態,以下:
1 @ThreadSafe 2 public class CountingFactorizer implements Servlet { 3 private final AtomicLong count = new AtomicLong(0); 4 //使用線程安全類AtomicLong來管理count這個狀態 5 6 public long getCount() { 7 return count.get(); 8 } 9 10 public void service(ServletRequest req, ServletResponse resp) { 11 BigInteger i = extractFromRequest(req); 12 BigInteger[] factors = factor(i); 13 count.incrementAndGet(); 14 encodeIntoResponse(resp, factors); 15 } 16 }
但這種方式沒法知足check-then-act這一類競態條件問題,以下:
1 @NotThreadSafe 2 public class UnsafeCachingFactorizer implements Servlet { 3 private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>(); 4 private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>(); 5 6 public void service(ServletRequest req, ServletResponse resp) { 7 BigInteger i = extractFromRequest(req); 8 if (i.equals(lastNumber.get())) 9 encodeIntoResponse(resp, lastFactors.get()); 10 else { 11 BigInteger[] factors = factor(i); 12 lastNumber.set(i); 13 lastFactors.set(factors); 14 encodeIntoResponse(resp, factors); 15 } 16 } 17 }
鎖Locking能夠更完美的解決複合操做的原子性問題。固然鎖也能夠解決變量的可見性問題。
也稱爲monitor locks監視器鎖,每個Java對象均可以被當成一個鎖,自動完成鎖的獲取和釋放,使用方式以下:
1 synchronized (lock) { 2 // Access or modify shared state guarded by lock 3 }
同時,內置鎖也是可重入的(Reentrancy),每一個鎖含有兩個狀態,一是獲取計數器(acquisition count),一個是全部者線程(owning thread),當count=0,鎖是可獲取狀態,當一個thread t1 獲取了一個count=0的鎖時,jvm設置這個鎖的count=1,owning thread=t1,當t1再次要獲取這個鎖時,是被容許的(便可重入),此時count++,當t1退出該同步代碼塊時,count--,直到count=0後,即鎖被t1完全釋放。
可見性比較難發現問題,是由於老是與咱們的直覺相違背。
重排序(reordering)的存在,易形成失效數據(Stale data),但這些數據多數都是以前某一個線程留下來的數據,而非隨機值,咱們稱這種狀況爲最低安全性(out-of-thin-air safety);但非原子的64位操做(如long,double),涉及到高位和低位分解爲2個32位操做的狀況,而沒法知足最低安全性,線程讀到的數據,多是線程A留下的高位和線程B留下的低位組合。除非用volatile關鍵字或鎖保護起來。
volatile關鍵字修飾的變量會避免與其餘內存操做重排序。慎用!
發佈:使對象可以在當前做用域外被使用。
逸出:不該該發佈的對象被髮布時。
隱式this指針逸出問題:
1 public class ThisEscape { 2 private String name = null; 3 4 public ThisEscape(EventSource source) { 5 source.registerListener(new EventListener() { 6 public void onEvent(Event event) { 7 doSomething(event); 8 } 9 }); 10 name = "TEST"; 11 } 12 13 protected void doSomething(Event event) { 14 System.out.println(name.toString()); 15 } 16 } 17 // Interface 18 import java.awt.Event; 19 20 public interface EventListener { 21 public void onEvent(Event event); 22 } 23 // class 24 public class EventSource { 25 public void registerListener(EventListener listener) { 26 listener.onEvent(null); 27 } 28 } 29 // Main 30 public class Client { 31 public static void main(String[] args) throws InterruptedException { 32 EventSource es = new EventSource(); 33 new ThisEscape(es); 34 } 35 }
修改以下,避免This逸出:
1 public class SafePublish { 2 3 private final EventListener listener; 4 private String name = null; 5 6 private SafePublish() { 7 listener = new EventListener() { 8 public void onEvent(Event event) { 9 doSomething(); 10 } 11 }; 12 name = "TEST"; 13 } 14 15 public static SafePublish newInstance(EventSource eventSource) { 16 SafePublish safePublish = new SafePublish (); 17 eventSource.registerListener(safeListener.listener); 18 return safePublish; 19 } 20 21 protected void doSomething() { 22 System.out.println(name.toString()); 23 } 24 }
形成this指針逸出的狀況:
如Swing 和 JDBC的實現,使用局部變量(local variables )和 ThreadLocal 類
ad-hoc線程封閉:不太懂,就是開發者本身去維護封閉性?
Stack confinement棧封閉
並非被final修飾的就是絕對的不可變!!
使用Volatile來發布不可變對象
1 @Immutable 2 class OneValueCache { 3 private final BigInteger lastNumber; 4 private final BigInteger[] lastFactors; 5 6 public OneValueCache(BigInteger i, BigInteger[] factors) { 7 lastNumber = i; 8 lastFactors = Arrays.copyOf(factors, factors.length); 9 } 10 11 public BigInteger[] getFactors(BigInteger i) { 12 if (lastNumber == null || !lastNumber.equals(i)) 13 return null; 14 else 15 return Arrays.copyOf(lastFactors, lastFactors.length); 16 } 17 } 18 19 // @ThreadSafe 20 public class VolatileCachedFactorizer implements Servlet { 21 private volatile OneValueCache cache = new OneValueCache(null, null); 22 23 public void service(ServletRequest req, ServletResponse resp) { 24 BigInteger i = extractFromRequest(req); 25 BigInteger[] factors = cache.getFactors(i); 26 if (factors == null) { 27 factors = factor(i); 28 cache = new OneValueCache(i, factors); 29 } 30 encodeIntoResponse(resp, factors); 31 } 32 } 33