執行緒安全性的文件化
阿新 • • 發佈:2018-12-05
首先說一個錯誤的觀點是“只要是加了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)實現同步而對彼此造成干擾。