? ? ? ? ?本系列譯自jakob jenkov的Java并發多線程教程,個人覺得很有收獲。由于個人水平有限,不對之處還望矯正!
? ? ? 代碼被多個線程同時調用是安全的,那么就稱之為線程安全。如果一段代碼是線程安全的,那么它沒有競態條件。競態條件只有發生在多個線程更新共享資源。因些,清楚的知道線程執行時什么資源是共享的非常重要。
本地變量
? ? ? ?本地變量存儲在每個線程自己的棧里,這就意味著本地變量從不與其他線程共享。也就是說本地變量是線程安全的,下面是關于線程安全的本地變量的一個例子:
public void someMethod(){
? ?long threadSafeInt =0;
? ?threadSafeInt++;
}
本地對象引用
? ? ? ?本地引用對象有點不同,它們引用它們自己,本地引用對象不存儲在本地棧中,而是存儲在共享堆中。如果一個引用對象只在創建他的方法內部使用,那么它是線程安全的,實事上,你也經常把它們傳給別的方法和對象。
下面這個是關于本地對象線程安全的例子
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作為其他方法的參數使用,在某種程度上來說,它是可以被其他線程訪問。
對象的成員變量
對象的成員變量是和對象一起存儲在堆里的,因此,如果兩個線程同時訪問一個方法的相同對象并且這個方法會更新這個成員變量時,這個方法就不是線程安全的。下面是是一個線程不安全的方法。
public class NotThreadSafe{
? ? StringBuilder builder = new StringBuilder();
? ? public void add(String text){
? ? ? ?this.builder.append(text);
? ?}
}
? ? ? 如果兩個線程同時調用同一個NotThreadSafe實例的add()方法時,會導致競態條件。例如:
NotThreadSafe sharedInstance = ?new NotThreadSafe();
new Thread(new MyRunnable(sharedInstance)).start();
new Thread(new MyRunnable(sharedInstance)).start();
public class MyRunnable implements Runnable{
? ? ? ?NotThreadSate instance = null;
? ? ? ?public MyRunable(NotThreadSafe instance){
? ? ? ? ? ? ?this.instance = instance;
? ? ? ?}
? ? ? @override
? ? ? public void run(){
? ? ? ? ? this.instance.add("some text");
? ? ? }
}
注意,兩個MyRunnable線程實例共享一個NotThreadSafe實例,因此,當他們NotThreadSafe實例上調用add()方法時,會導致競態條件。
然而,當兩個線程同時在不同的NotThreadSafe實例上調用add()方法時,不會導致競態條件的產生。下面是之前的例子,只是稍作修改:
new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();
現在,兩個線程都有它們自己的NotThreadSafe實例,因此,當它們調用add()方法時,它們互不干擾。上面的代碼也不會競態條件。因此,即使是線程不安全的對象,仍然可以通過其他方式讓它們不會產生競態條件。
線程逃逸法則
當他嘗試確認你的代碼訪問確定資源是否是線程安全的,你可以采用線程逃逸法則:
If a resource is created,used and disposed whthin the control of the same thread,and never escapes the control of this thread,the use of that resource is thread safe.
這里共享資源可以是一個對象、數組、文件、數據庫連接、socket等,在java言中,你不可能清楚的知道對象是否銷毀,銷毀意味著失去對象的引用或是對象為null.
即使對象的引用是線程安全的,但是如果這個對象指向的是共享資源如文件或是數據庫,你的應用有可能也不是線程不安全的。例如:線程1和線程2都各自己創建他們的數據庫連接,他們各自的數據庫連接是線程安全的,但是使用數據庫的連接可能不是線程安全的。例如:如果兩個線程如下面的代碼一樣執行。
check if record X exists
if not insert record x
如果兩個線程同時執行,record x是檢測的是同一條記錄,這里有個風險就是兩個線程都插入了record x
Thread 1 checks if record x exists. Result = no
Thread 2 checks if record x exists. Result = no
Thread1 insert record x
Thread2 insert record x
這種情況也可能發生在線程操作文件或是其他的共享資源,因此,區分線程控制對象,還是僅僅引用對象是非常重要的。