1. 程式人生 > >java中同步(synchronized)訪問共享的可變資料及原子性操作

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();
    }