本文爲轉載學習html
原文連接:http://tutorials.jenkov.com/java-concurrency/thread-safety.htmljava
譯文連接:http://ifeve.com/thread-safety/數據庫
容許被多個線程同時執行的代碼稱做線程安全的代碼。線程安全的代碼不包含競態條件。當多個線程同時更新共享資源時會引起競態條件。所以,瞭解Java線程執行時共享了什麼資源很重要。數組
局部變量存儲在線程本身的棧中。也就是說,局部變量永遠也不會被多個線程共享。因此,基礎類型的局部變量是線程安全的。下面是基礎類型的局部變量的一個例子:安全
public void someMethod(){ long threadSafeInt = 0; threadSafeInt++; }
對象的局部引用和基礎類型的局部變量不太同樣。儘管引用自己沒有被共享,但引用所指的對象並無存儲在線程的棧內。全部的對象都存在共享堆中。若是在某個方法中建立的對象不會逃逸出(譯者注:即該對象不會被其它方法得到,也不會被非局部變量引用到)該方法,那麼它就是線程安全的。實際上,哪怕將這個對象做爲參數傳給其它方法,只要別的線程獲取不到這個對象,那它還是線程安全的。下面是一個線程安全的局部引用樣例:app
public void someMethod(){ LocalObject localObject = new LocalObject(); localObject.callMethod(); method2(localObject); } public void method2(LocalObject localObject){ localObject.setValue("value"); }
樣例中LocalObject對象沒有被方法返回,也沒有被傳遞給someMethod()方法外的對象。每一個執行someMethod()的線程都會建立本身的LocalObject對象,並賦值給localObject引用。所以,這裏的LocalObject是線程安全的。事實上,整個someMethod()都是線程安全的。即便將LocalObject做爲參數傳給同一個類的其它方法或其它類的方法時,它仍然是線程安全的。固然,若是LocalObject經過某些方法被傳給了別的線程,那它就再也不是線程安全的了。學習
對象成員存儲在堆上。若是兩個線程同時更新同一個對象的同一個成員,那這個代碼就不是線程安全的。下面是一個樣例:ui
public class NotThreadSafe{ StringBuilder builder = new StringBuilder(); public add(String text){ this.builder.append(text); } }
若是兩個線程同時調用同一個NotThreadSafe
實例上的add()方法,就會有競態條件問題。例如:this
NotThreadSafe sharedInstance = new NotThreadSafe(); new Thread(new MyRunnable(sharedInstance)).start(); new Thread(new MyRunnable(sharedInstance)).start(); public class MyRunnable implements Runnable{ NotThreadSafe instance = null; public MyRunnable(NotThreadSafe instance){ this.instance = instance; } public void run(){ this.instance.add("some text"); } }
注意兩個MyRunnable共享了同一個NotThreadSafe對象。所以,當它們調用add()方法時會形成競態條件。spa
固然,若是這兩個線程在不一樣的NotThreadSafe實例上調用call()方法,就不會致使競態條件。下面是稍微修改後的例子:
new Thread(new MyRunnable(new NotThreadSafe())).start(); new Thread(new MyRunnable(new NotThreadSafe())).start();
如今兩個線程都有本身單獨的NotThreadSafe對象,調用add()方法時就會互不干擾,不再會有競態條件問題了。因此非線程安全的對象仍能夠經過某種方式來消除競態條件。
線程控制逃逸規則能夠幫助你判斷代碼中對某些資源的訪問是不是線程安全的。
若是一個資源的建立,使用,銷燬都在同一個線程內完成, 且永遠不會脫離該線程的控制,則該資源的使用就是線程安全的。
資源能夠是對象,數組,文件,數據庫鏈接,套接字等等。Java中你無需主動銷燬對象,因此「銷燬」指再也不有引用指向對象。
即便對象自己線程安全,但若是該對象中包含其餘資源(文件,數據庫鏈接),整個應用也許就再也不是線程安全的了。好比2個線程都建立了各自的數據庫鏈接,每一個鏈接自身是線程安全的,但它們所鏈接到的同一個數據庫也許不是線程安全的。好比,2個線程執行以下代碼:
檢查記錄X是否存在,若是不存在,插入X
若是兩個線程同時執行,並且碰巧檢查的是同一個記錄,那麼兩個線程最終可能都插入了記錄:
線程1檢查記錄X是否存在。檢查結果:不存在 線程2檢查記錄X是否存在。檢查結果:不存在 線程1插入記錄X 線程2插入記錄X
一樣的問題也會發生在文件或其餘共享資源上。所以,區分某個線程控制的對象是資源自己,仍是僅僅到某個資源的引用很重要。