首先說一個錯誤的觀點是“只要是加了synchronized關鍵字的方法或者代碼塊就一定是線程安全的,而沒有加這個關鍵字的代碼就不是線程安全的”。這種觀點認爲“線程安全要麼全有要麼全無”,事實上這是錯誤的。因爲線程安全包含了幾種級別:
- 不可變的(Immutable):類的實例不可變(不可變類),一定線程安全,如String、Long、BigInteger等。
- 無條件的線程安全(Unconditionally ThreadSafe):該類的實例是可變的,但是這個類有足夠的的內部同步。所以,它的實例可以被併發使用,無需任何外部同步,如Random和ConcurrentHashMap。
- 有條件的線程安全(Conditionally ThreadSafe):某些方法需要爲了安全的併發而在外部進行同步,其餘方法與無條件的線程安全一致。如Collection.synchronized返回的集合,對它們進行迭代時就需要外部同步。如下代碼,當對synchronizeColletcion返回的 collection進行迭代時,用戶必須手工在返回的 collection 上進行同步,不遵從此建議將導致無法確定的行爲。
Collection c = Collections.synchronizedCollection(myCollection); ... synchronized(c) { Iterator i = c.iterator(); // Must be in the synchronized block while (i.hasNext()) foo(i.next()); }
- 非線程安全(UnThreadSafe):該類是實例可變的,如需安全地併發使用,必須外部手動同步。如HashMap和HashSet。
- 線程對立的(thread-hostile):即便所有的方法都被外部同步保衛,這個類仍不能安全的被多個線程併發使用。這種情況的類很少,不常用。
以上是常用的5種線程安全性的級別,這些級別應該認真編寫在類的線程安全註解中,以讓用戶清楚的知道某個類的線程安全性。synchronized關鍵字與這個文檔毫無關係。
Conditionally-ThreadSafe類必須在文檔中指明“哪個方法調用序列需要外部同步,以及在執行這些序列的時候要獲得哪把鎖。”
如果正在編寫的是無條件的線程安全類,就應該考慮使用私有的鎖對象來代替同步方法,這樣可以防止客戶端程序和子類的不同步干擾。下面的代碼體現了使用同步方法會造成的子類在無意之中妨礙基類的操作。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/**
* 這個程序的運行結果是concurrent1和concurrent2隨機出現,syn1都在syn2之後或者之前。
* 由此可說明使用私有對象鎖可以避免父子類方法的互相同步的干擾問題。
* 因此,在Effective Java中說私有對象鎖尤其適用於那些專門爲繼承而設計的類中。
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
final CountDownLatch start = new CountDownLatch(1);
final CyclicBarrier firstSectionOver = new CyclicBarrier(2);
final Son son = new Son();
new Thread(new Runnable() {
public void run() {
try {
start.await();
son.syn1();
firstSectionOver.await();
son.concurrent1();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
try {
start.await();
son.syn2();
firstSectionOver.await();
son.concurrent2();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
start.countDown();
}
}
class Parent {
private final Object parentLock = new Object();
public synchronized void syn1() throws InterruptedException {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println("syn1");
}
}
public void concurrent1() throws InterruptedException {
synchronized (parentLock) {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println("concurrent1");
}
}
}
}
class Son extends Parent {
private final Object sonLock = new Object();
public synchronized void syn2() throws InterruptedException {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println("syn2,has no concern about the Method syn1");
}
}
public void concurrent2() throws InterruptedException {
synchronized (sonLock) {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println("concurrent2,,has no concern about the Method concurrent1");
}
}
}
}
在上面的代碼中,syn2和concurrent2都想成爲一個不干擾父類方法並且也不被父類方法干擾的同步方法。從運行的結果來看,concurrent2做到了,但syn2沒做到。syn2和syn1由於使用synchronized(this)實現同步而對彼此造成干擾。