? ? ? ? ?本系列譯自jakob jenkov的Java并發(fā)多線程教程,個人覺得很有收獲。由于個人水平有限祭往,不對之處還望矯正荡含!
? ? ? 代碼被多個線程同時調(diào)用是安全的,那么就稱之為線程安全甚垦。如果一段代碼是線程安全的茶鹃,那么它沒有競態(tài)條件。競態(tài)條件只有發(fā)生在多個線程更新共享資源艰亮。因些前计,清楚的知道線程執(zhí)行時什么資源是共享的非常重要。
本地變量
? ? ? ?本地變量存儲在每個線程自己的棧里垃杖,這就意味著本地變量從不與其他線程共享男杈。也就是說本地變量是線程安全的,下面是關(guān)于線程安全的本地變量的一個例子:
public void someMethod(){
? ?long threadSafeInt =0;
? ?threadSafeInt++;
}
本地對象引用
? ? ? ?本地引用對象有點不同调俘,它們引用它們自己伶棒,本地引用對象不存儲在本地棧中,而是存儲在共享堆中彩库。如果一個引用對象只在創(chuàng)建他的方法內(nèi)部使用肤无,那么它是線程安全的,實事上骇钦,你也經(jīng)常把它們傳給別的方法和對象宛渐。
下面這個是關(guān)于本地對象線程安全的例子
public void someMethod{
? ?LocalObject localObject = new LocalObject();
? ?localObject.callMethod();
? ?method2(localObject);
}
public void method2(LocalObject localObject){
? ? localObject.setValue("value");
}
在上面的例子中,localObject這個實例當方法調(diào)用時沒有返回值眯搭, 在someMethod()方法之外窥翩,它也把它傳給其他訪問對象 ?,每個線程執(zhí)行someMethod()方法時鳞仙,會創(chuàng)建一個屬于它自己的localObject實例寇蚊,并且分配給它localObject的引用,因此棍好,在這里使用localObject是線程安全的仗岸。事實上,整個someMethod()方法都是線程安全的借笙。盡管localObject被作為參數(shù)傳給同一類中的其他方法扒怖,或者傳給其他類,它的使用都是安全的业稼。唯一的例外就是盗痒,如果一個方法把localObject作為其他方法的參數(shù)使用,在某種程度上來說盼忌,它是可以被其他線程訪問积糯。
對象的成員變量
對象的成員變量是和對象一起存儲在堆里的掂墓,因此,如果兩個線程同時訪問一個方法的相同對象并且這個方法會更新這個成員變量時看成,這個方法就不是線程安全的君编。下面是是一個線程不安全的方法。
public class NotThreadSafe{
? ? StringBuilder builder = new StringBuilder();
? ? public void add(String text){
? ? ? ?this.builder.append(text);
? ?}
}
? ? ? 如果兩個線程同時調(diào)用同一個NotThreadSafe實例的add()方法時川慌,會導(dǎo)致競態(tài)條件吃嘿。例如:
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實例上調(diào)用add()方法時,會導(dǎo)致競態(tài)條件琴拧。
然而降瞳,當兩個線程同時在不同的NotThreadSafe實例上調(diào)用add()方法時,不會導(dǎo)致競態(tài)條件的產(chǎn)生蚓胸。下面是之前的例子挣饥,只是稍作修改:
new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();
現(xiàn)在,兩個線程都有它們自己的NotThreadSafe實例沛膳,因此扔枫,當它們調(diào)用add()方法時,它們互不干擾锹安。上面的代碼也不會競態(tài)條件短荐。因此,即使是線程不安全的對象叹哭,仍然可以通過其他方式讓它們不會產(chǎn)生競態(tài)條件忍宋。
線程逃逸法則
當他嘗試確認你的代碼訪問確定資源是否是線程安全的,你可以采用線程逃逸法則:
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.
這里共享資源可以是一個對象话速、數(shù)組讶踪、文件、數(shù)據(jù)庫連接泊交、socket等,在java言中柱查,你不可能清楚的知道對象是否銷毀廓俭,銷毀意味著失去對象的引用或是對象為null.
即使對象的引用是線程安全的,但是如果這個對象指向的是共享資源如文件或是數(shù)據(jù)庫唉工,你的應(yīng)用有可能也不是線程不安全的研乒。例如:線程1和線程2都各自己創(chuàng)建他們的數(shù)據(jù)庫連接,他們各自的數(shù)據(jù)庫連接是線程安全的淋硝,但是使用數(shù)據(jù)庫的連接可能不是線程安全的雹熬。例如:如果兩個線程如下面的代碼一樣執(zhí)行宽菜。
check if record X exists
if not insert record x
如果兩個線程同時執(zhí)行,record x是檢測的是同一條記錄竿报,這里有個風(fēng)險就是兩個線程都插入了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
這種情況也可能發(fā)生在線程操作文件或是其他的共享資源铅乡,因此,區(qū)分線程控制對象烈菌,還是僅僅引用對象是非常重要的阵幸。