Android基礎進階之EffectiveJava翻譯系列(第九章:併發)

​ 線程允許多個活動頁面同時執行.併發編程比單線程難,因爲很多事情一起處理容易出錯,也很難減少錯誤,但是你不能避免併發.這章幫助你編寫簡潔的,正確的,良好閱讀性的併發編程

Item66 同步共享的可變數據

​ synchronized 關鍵字可以保證一次只有一個線程訪問代碼塊,許多開發者認爲同步就是一種互斥,防止對象在另一個線程修改時處於不一致的狀態.在這種觀點中,對象處於一種正確的狀態,因爲訪問它的方法鎖住了.這些方法確保對象的狀態由一種狀態安全的轉移到另一種狀態.

​ 這種觀點只正確了一半,不同步的話,一個線程的改變對其它線程是不可見的.通過相同的鎖,同步不僅阻止線程在不一致狀態下觀察對象,而且確保每個進入同步方法或塊的線程都能看到所有一致性的效果.

​ 考慮一下從一個線程停止另一個線程,Java lib提供了Thread.stop方法,但是這個方法被遺棄了,因爲它是不安全的---將導致數據損壞.一種建議方法是獲取到第一個線程的boolean變量,一些人可能會這麼寫:

//bad
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args)
    throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
            int i = 0;
            while (!stopRequested)
                i++;
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
        }
}

​ 你可能期望這個程序運行大約一秒,然後主線程設置stopRequested爲true,從而導致後臺線程的循環終止 .然而在我的機器上,程序永遠不會停:子線程永遠在循環!

​ 問題在於,在沒有同步的情況下,無法保證後臺線程何時會看到主進程所做的修改. 在沒有同步的情況下,虛擬機轉換成以下代碼:

while (!done)
    i++;
//轉換
if (!done)
    while (true)
        i++;

修復方式如下:

//good
public class StopThread {
    private static boolean stopRequested;
    private static synchronized void requestStop() {
        stopRequested = true;
    }
    private static synchronized boolean stopRequested() {
        return stopRequested;
    }
public static void main(String[] args)
    throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested())
                    i++;
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
} 

注意:讀和寫都是同步的,光對寫方法同步,同步會失效

還可以使用volatile關鍵字修復爲:

public class StopThread {
    private static volatile boolean stopRequested;
    public static void main(String[] args)
        throws InterruptedException {
            Thread backgroundThread = new Thread(new Runnable() {
                public void run() {
                    int i = 0;
                    while (!stopRequested)
                        i++;
                }
            });
            backgroundThread.start();
            TimeUnit.SECONDS.sleep(1);
            stopRequested = true;
    }
}

但使用volatile關鍵字要小心,考慮如下代碼

//bad
private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
    return nextSerialNumber++;
} 

乍看之下沒有什麼問題,但是"++"操作不是原子性的,包含了兩個操作,一個是讀舊值,另一個是在舊值的基礎上加一,在賦值.修復方式爲加上synchronized關鍵字:

//good
private static volatile int nextSerialNumber = 0;
public static synchronized int generateSerialNumber() {
    return nextSerialNumber++;
} 

​ 避免此類問題最好的方式是不要共享可變數據.要麼共享不可變的數據,要麼就不共享.換句話說,在一個線程中定義可變數據.如果採用此策略,則必須將其文檔化,以便程序維護此原則

總之,當多個線程共享數據時,讀取或寫入數據的每個線程都必須執行同步.沒有同步,無法保證一個線程的修改對另一個線程可見.這將會導致程序安全問題,而且很難調試.如果你只需要內部間的線程通信而不考慮互斥, volatile 關鍵字可以替代synchronized,但是volatile很難被正確使用


第八章:異常

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章