允許被多個(gè)線程同時(shí)執(zhí)行的代碼稱作線程安全的代碼撤蟆。線程安全的代碼不包含競態(tài)條件火脉。當(dāng)多個(gè)線程同時(shí)更新共享資源時(shí)會(huì)引發(fā)競態(tài)條件牵舵。因此茅特,了解Java線程執(zhí)行時(shí)共享了什么資源很重要。
局部變量
局部變量存儲(chǔ)在線程自己的棧中棋枕。也就是說,局部變量永遠(yuǎn)也不會(huì)被多個(gè)線程共享妒峦。所以重斑,基礎(chǔ)類型的局部變量是線程安全的。下面是基礎(chǔ)類型的局部變量的一個(gè)例子:
publicvoid someMethod(){
? longthreadSafeInt = 0;
? threadSafeInt++;
}
局部的對象引用
對象的局部引用和基礎(chǔ)類型的局部變量不太一樣肯骇。盡管引用本身沒有被共享窥浪,但引用所指的對象并沒有存儲(chǔ)在線程的棧內(nèi)。所有的對象都存在共享堆中笛丙。如果在某個(gè)方法中創(chuàng)建的對象不會(huì)逃逸出(譯者注:即該對象不會(huì)被其它方法獲得漾脂,也不會(huì)被非局部變量引用到)該方法,那么它就是線程安全的胚鸯。實(shí)際上骨稿,哪怕將這個(gè)對象作為參數(shù)傳給其它方法,只要?jiǎng)e的線程獲取不到這個(gè)對象姜钳,那它仍是線程安全的坦冠。下面是一個(gè)線程安全的局部引用樣例:
publicvoid someMethod(){
? LocalObject localObject =new LocalObject();
? localObject.callMethod();
? method2(localObject);
}publicvoid method2(LocalObject localObject){
? localObject.setValue("value");
}
樣例中LocalObject對象沒有被方法返回,也沒有被傳遞給someMethod()方法外的對象哥桥。每個(gè)執(zhí)行someMethod()的線程都會(huì)創(chuàng)建自己的LocalObject對象辙浑,并賦值給localObject引用。因此拟糕,這里的LocalObject是線程安全的判呕。事實(shí)上,整個(gè)someMethod()都是線程安全的送滞。即使將LocalObject作為參數(shù)傳給同一個(gè)類的其它方法或其它類的方法時(shí)侠草,它仍然是線程安全的。當(dāng)然犁嗅,如果LocalObject通過某些方法被傳給了別的線程梦抢,那它就不再是線程安全的了。
對象成員
對象成員存儲(chǔ)在堆上愧哟。如果兩個(gè)線程同時(shí)更新同一個(gè)對象的同一個(gè)成員奥吩,那這個(gè)代碼就不是線程安全的。下面是一個(gè)樣例:
publicclass NotThreadSafe{
? ? StringBuilder builder =new StringBuilder();
? ? public add(String text){
? ? ? ? this.builder.append(text);
? ? }? ?
}
如果兩個(gè)線程同時(shí)調(diào)用同一個(gè)NotThreadSafe實(shí)例上的add()方法蕊梧,就會(huì)有競態(tài)條件問題霞赫。例如:
NotThreadSafe sharedInstance =new NotThreadSafe();newThread(new MyRunnable(sharedInstance)).start();newThread(new MyRunnable(sharedInstance)).start();publicclassMyRunnableimplements Runnable{
? NotThreadSafe instance =null;
? public MyRunnable(NotThreadSafe instance){
? ? this.instance = instance;
? }
? publicvoid run(){
? ? this.instance.add("some text");
? }
}
注意兩個(gè)MyRunnable共享了同一個(gè)NotThreadSafe對象。因此肥矢,當(dāng)它們調(diào)用add()方法時(shí)會(huì)造成競態(tài)條件端衰。
當(dāng)然叠洗,如果這兩個(gè)線程在不同的NotThreadSafe實(shí)例上調(diào)用call()方法,就不會(huì)導(dǎo)致競態(tài)條件旅东。下面是稍微修改后的例子:
newThread(newMyRunnable(new NotThreadSafe())).start();newThread(newMyRunnable(newNotThreadSafe())).start();
現(xiàn)在兩個(gè)線程都有自己單獨(dú)的NotThreadSafe對象灭抑,調(diào)用add()方法時(shí)就會(huì)互不干擾,再也不會(huì)有競態(tài)條件問題了抵代。所以非線程安全的對象仍可以通過某種方式來消除競態(tài)條件腾节。
線程控制逃逸規(guī)則
線程控制逃逸規(guī)則可以幫助你判斷代碼中對某些資源的訪問是否是線程安全的。
如果一個(gè)資源的創(chuàng)建荤牍,使用案腺,銷毀都在同一個(gè)線程內(nèi)完成,
且永遠(yuǎn)不會(huì)脫離該線程的控制康吵,則該資源的使用就是線程安全的劈榨。
資源可以是對象,數(shù)組晦嵌,文件同辣,數(shù)據(jù)庫連接,套接字等等惭载。Java中你無需主動(dòng)銷毀對象邑闺,所以“銷毀”指不再有引用指向?qū)ο蟆?/p>
即使對象本身線程安全,但如果該對象中包含其他資源(文件棕兼,數(shù)據(jù)庫連接)陡舅,整個(gè)應(yīng)用也許就不再是線程安全的了。比如2個(gè)線程都創(chuàng)建了各自的數(shù)據(jù)庫連接伴挚,每個(gè)連接自身是線程安全的靶衍,但它們所連接到的同一個(gè)數(shù)據(jù)庫也許不是線程安全的。比如茎芋,2個(gè)線程執(zhí)行如下代碼:
檢查記錄X是否存在颅眶,如果不存在,插入X
如果兩個(gè)線程同時(shí)執(zhí)行田弥,而且碰巧檢查的是同一個(gè)記錄涛酗,那么兩個(gè)線程最終可能都插入了記錄:
線程1檢查記錄X是否存在。檢查結(jié)果:不存在
線程2檢查記錄X是否存在偷厦。檢查結(jié)果:不存在
線程1插入記錄X
線程2插入記錄X
同樣的問題也會(huì)發(fā)生在文件或其他共享資源上商叹。因此,區(qū)分某個(gè)線程控制的對象是資源本身只泼,還是僅僅到某個(gè)資源的引用很重要剖笙。