允許被多個線程同時執(zhí)行的代碼稱作線程安全的代碼。線程安全的代碼不包含競態(tài)條件。當多個線程同時更新共享資源時會引發(fā)競態(tài)條件。因此,了解Java線程執(zhí)行時共享了什么資源很重要。
局部變量
局部變量存儲在線程自己的棧中。也就是說,局部變量永遠也不會被多個線程共享。所以,基礎(chǔ)類型的局部變量是線程安全的。下面是基礎(chǔ)類型的局部變量的一個例子:
publicvoid someMethod(){
? longthreadSafeInt = 0;
? threadSafeInt++;
}
局部的對象引用
對象的局部引用和基礎(chǔ)類型的局部變量不太一樣。盡管引用本身沒有被共享,但引用所指的對象并沒有存儲在線程的棧內(nèi)。所有的對象都存在共享堆中。如果在某個方法中創(chuàng)建的對象不會逃逸出(譯者注:即該對象不會被其它方法獲得,也不會被非局部變量引用到)該方法,那么它就是線程安全的。實際上,哪怕將這個對象作為參數(shù)傳給其它方法,只要別的線程獲取不到這個對象,那它仍是線程安全的。下面是一個線程安全的局部引用樣例:

publicvoid someMethod(){
? LocalObject localObject =new LocalObject();
? localObject.callMethod();
? method2(localObject);
}publicvoid method2(LocalObject localObject){
? localObject.setValue("value");
}

樣例中LocalObject對象沒有被方法返回,也沒有被傳遞給someMethod()方法外的對象。每個執(zhí)行someMethod()的線程都會創(chuàng)建自己的LocalObject對象,并賦值給localObject引用。因此,這里的LocalObject是線程安全的。事實上,整個someMethod()都是線程安全的。即使將LocalObject作為參數(shù)傳給同一個類的其它方法或其它類的方法時,它仍然是線程安全的。當然,如果LocalObject通過某些方法被傳給了別的線程,那它就不再是線程安全的了。
對象成員
對象成員存儲在堆上。如果兩個線程同時更新同一個對象的同一個成員,那這個代碼就不是線程安全的。下面是一個樣例:

publicclass NotThreadSafe{
? ? StringBuilder builder =new StringBuilder();
? ? public add(String text){
? ? ? ? this.builder.append(text);
? ? }? ?
}

如果兩個線程同時調(diào)用同一個NotThreadSafe實例上的add()方法,就會有競態(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");
? }
}

注意兩個MyRunnable共享了同一個NotThreadSafe對象。因此,當它們調(diào)用add()方法時會造成競態(tài)條件。
當然,如果這兩個線程在不同的NotThreadSafe實例上調(diào)用call()方法,就不會導(dǎo)致競態(tài)條件。下面是稍微修改后的例子:
newThread(newMyRunnable(new NotThreadSafe())).start();newThread(newMyRunnable(newNotThreadSafe())).start();
現(xiàn)在兩個線程都有自己單獨的NotThreadSafe對象,調(diào)用add()方法時就會互不干擾,再也不會有競態(tài)條件問題了。所以非線程安全的對象仍可以通過某種方式來消除競態(tài)條件。
線程控制逃逸規(guī)則
線程控制逃逸規(guī)則可以幫助你判斷代碼中對某些資源的訪問是否是線程安全的。
如果一個資源的創(chuàng)建,使用,銷毀都在同一個線程內(nèi)完成,
且永遠不會脫離該線程的控制,則該資源的使用就是線程安全的。
資源可以是對象,數(shù)組,文件,數(shù)據(jù)庫連接,套接字等等。Java中你無需主動銷毀對象,所以“銷毀”指不再有引用指向?qū)ο蟆?/p>
即使對象本身線程安全,但如果該對象中包含其他資源(文件,數(shù)據(jù)庫連接),整個應(yīng)用也許就不再是線程安全的了。比如2個線程都創(chuàng)建了各自的數(shù)據(jù)庫連接,每個連接自身是線程安全的,但它們所連接到的同一個數(shù)據(jù)庫也許不是線程安全的。比如,2個線程執(zhí)行如下代碼:
檢查記錄X是否存在,如果不存在,插入X
如果兩個線程同時執(zhí)行,而且碰巧檢查的是同一個記錄,那么兩個線程最終可能都插入了記錄:
線程1檢查記錄X是否存在。檢查結(jié)果:不存在
線程2檢查記錄X是否存在。檢查結(jié)果:不存在
線程1插入記錄X
線程2插入記錄X
同樣的問題也會發(fā)生在文件或其他共享資源上。因此,區(qū)分某個線程控制的對象是資源本身,還是僅僅到某個資源的引用很重要。