java中同步(synchronized)訪問共享的可變資料及原子性操作
當多個執行緒共享可變資料的時候,每個讀或者寫資料的執行緒都必須執行同步。如果沒有同步,就無法保證一個執行緒所做的修改可以被另外一個執行緒獲知。未能同步共享可變資料會造成程式的活性失敗(liveness failure)和安全性失敗(safety failure)。這樣的失敗是最難以除錯的。它們可能是間歇性的,切與時間相關,程式的行為在不同的VM上可能根本不同。如果只需要執行緒之間的互動通訊,而不需要互斥,volatile修飾符就是一種可以接受的同步形式,但要正確地使用它可能需要一些技巧。
“原子操作(atomic operation)是不需要synchronized”,這是Java多執行緒程式設計的老生常談了。所謂原子操作是指不會被執行緒排程機制打斷的操作;這種操作一旦開始,就一直執行到結束,中間不會有任何 context switch (切 [1] 換到另一個執行緒)。
1.1 關鍵字synchronized
關鍵字synchronized可以保證在同一時刻,只有一個執行緒可以執行某一個方法,或者某一個程式碼塊。
如下示例:
package thread;
import java.util.concurrent.TimeUnit;
public class StopThreadSynch {
private static boolean stopRequested;
public static synchronized boolean isStopRequested() {
return stopRequested;
}
public static synchronized void setStopRequested(boolean stopRequested) {
StopThreadSynch.stopRequested = stopRequested;
}
public static void main(String[] args) throws InterruptedException {
long startDate = System.currentTimeMillis();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while(!isStopRequested()) {
i++;
System.out.println(i);
}
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
setStopRequested(true);
long endDate = System.currentTimeMillis();
System.out.println(endDate - startDate);
System.out.println("-----------");
}
}
注意寫方法和讀方法都是被同步了,如果只有寫方法被同步而讀方法沒有被同步,同步就不會起作用。
1.2 看如下的例子
private static int nextNumber = 0;
public static synchronized int generateNumber() {
return nextNumber++;
}
這個方法的目的是要確保每個呼叫都返回不同的值(只要不超過2的32次方),所以在方法塊上加了synchronized同步關鍵字,這樣可以確保多個呼叫不會交叉存取,確保每個呼叫都會看到之前所有的呼叫的效果。
另外一種方法是使用java.util.concurrent.atomic的一部分,這個包下面有我們常用的幾個類:
java.util.concurrent.atomic.AtomicBoolean,
java.util.concurrent.atomic.AtomicInteger,
java.util.concurrent.atomic.AtomicIntegerArray,
java.util.concurrent.atomic.AtomicLong,
java.util.concurrent.atomic.AtomicLongArray
它們所做的正是我們想要的,而且有可能比同步版(synchronized)執行的更好:
private static final AtomicInteger nextNumber = new AtomicInteger();
public static int generateNumber() {
return nextNumber.getAndIncrement();
}