1. 程式人生 > >Java同步(Synchronization)

Java同步(Synchronization)

對象引用 例子 修復 gif 存儲 順序 分享圖片 tutorial 意義

前言

線程間的通信主要通過共享對字段的訪問和對象引用字段的引用,可能會產生兩種錯誤,線程幹擾和內存一致性錯誤。Java的同步就是防止這些錯誤,但當多個線程訪問同一資源會導致線程執行緩慢,甚至暫停執行。

線程幹擾(Thread Interference)

例子

技術分享圖片
class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        
return c; } }
View Code

如果現在有兩個線程,線程A執行increment,線程B執行decrement,它們都使用了相同的變量c,這時會發生幹擾。即便是執行非常簡單的語句,但簡單語句也可以轉化為Java虛擬機的多個步驟,例如c++可以分解為三個步驟:

1、檢索c的當前值。

2、將檢索值加1。

3、將值存儲回c中。

PS:c—也可以分解為相同步驟,只是第二步是減1。

假如線程A和線程B幾乎同時調用,c的初始值為0,則它們交錯的步驟可能是以下順序:

線程A:檢索c。
線程B:檢索c。
線程A:增加檢索值;結果是1。
線程B:減少檢索值;結果是-1。
線程A:將結果存儲在c中; c現在是1。
線程B:將結果存儲在c中; c現在是
-1。

線程A的結果丟失了,被線程B覆蓋了。這種交錯只是一種可能性,也可能是B的結果丟失,也可能不會出錯。也就是因為順序是不可預測的,所以線程幹擾的錯誤可能難以發現和修復。

內存一致性錯誤

不同線程看到的內存中的同一份數據卻有不同的“視圖”,原因很復雜,作為程序員最需要做的是處理好“happens-before”的關系,避免問題。

同步方法

Java提供了兩種基本的同步方法:同步方法和同步語句。同步的行為是依賴鎖來構建的,每一個對象都有與之相關的固定鎖,想要獨占訪問對象的線程必須先獲取對象的鎖(拿不到阻塞線程),完成後釋放鎖,同時與請求同一鎖的線程建立一個happens-before關系。

同步方法的作用:

1、當一個線程正在執行一個對象的同步方法時,所有其他調用該對象的同步方法的線程將被阻塞,直到第一個線程執行完成。

2、當一個同步方法退出後,會自動建立與後續調用的同步方法(相同對象)的一個happens-before關系,保證所有線程對對象狀態的修改可見。

註意:

1、在構造函數中使用synchronized關鍵字是語法錯誤。同步構造函數沒有意義,因為只有創建對象的線程在構建時才能訪問它。

2、如果一個對象對多個線程可見,則通過同步方法完成對該對象變量的所有讀取或寫入操作,不然還是會出現線程幹擾和內存一致性錯誤。

技術分享圖片
public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}
View Code

同步語句

另一種創建同步代碼的方法,與同步方法不同,同步語句必須指定提供內部鎖的對象。同步語句有利於通過細粒度同步來提高並發性。

例如,假設類MsLunch具有兩個實例字段c1和c2,它們從不一起使用(很重要的前提條件!)。這些字段的所有更新都必須同步,但沒有理由阻止c1的更新與c2的更新交錯,這樣做會創建不必要的阻塞來降低並發性。我們不使用同步方法,而是創建兩個對象來提供鎖。

技術分享圖片
public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}
View Code

volatile關鍵字

原子操作:一個原子操作,要麽發生,要麽不發生。比如 c=0;(非long和double類型) 這個操作是執行了就會發生。再比如:c++;可分割成三個操作步驟,執行時可能會丟失某些步驟,就不是一個原子操作。非原子操作都會存在線程安全問題,同步方法和同步語句可以讓它變成一個原子操作。

volatile變量是一種稍弱的同步機制,所有聲明為volatile的變量(包括long和double),讀取和寫入都是原子的。

volatile變量特性:

1、當一個線程讀取一個volatile變量時,它看到總是最新的值。

2、原子動作不能交錯,使用volatile變量不用擔心線程幹擾。

PS:java.util.concurrent包下提供了一些原子類。

參考文獻

https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

Java同步(Synchronization)