[03] 線程同步 synchronized


一、線程同步概述

線程之間有可能共享一些資源,好比內存、文件、數據庫等。多個線程同時讀寫同一份共享資源時,就可能引發衝突,因此引入了線程的「同步」機制。

所謂 同步,就是說線程要有先來後到,排隊執行操做,而不是同時進行操做。目的就是爲了防止多個線程在訪問相同數據對象時,對數據形成污染和破壞。

爲了實現同步,Java中提供了「鎖」的機制,能夠給共享資源加上一把鎖,這把鎖只有一把鑰匙,哪一個線程獲取了這把鑰匙,纔有權利去訪問該共享資源。而實現「鎖」機制的關鍵字,就是 synchronized

二、synchronized

synchronized 的使用很簡單
  • 同步方法:   訪問權限修飾符  synchronized 數據返回類型 方法名() { ... }
  • 同步語句塊synchronized (共享對象名) { ... }
  • 不能修飾構造函數、抽象方法、成員變量

下面咱們來看個簡單的demo:
//Data 共享數據
public class Data {

    private static int count = 0;

    public static int getCount() {
        return count;
    }

    public static void add() {
        count++;
    }

}

//Counter 操做共享數據
public class Counter {

    public synchronized void count() {
        Data.add();
        System.out.println("current count = " + Data.getCount());
    }

}

//MyThread
public class MyThread extends Thread {

    private Counter counter;

    public MyThread(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for(int i = 0; i < 50; i++) {
            counter.count();
        }
    }

}


//Test
public class Test {
    public static void main(String[] args) {
        Counter counter1 = new Counter();
        Counter counter2 = new Counter();

        Thread t1 = new MyThread(counter1);
        Thread t2 = new MyThread(counter2);
        t1.start();
        t2.start();
    }
}

//輸出結果示例
...
current count = 2
current count = 2
current count = 3
current count = 4
current count = 5
current count = 6
current count = 7
...

如上例中,能夠看到在 Counter 類的 count() 方法咱們已經加上了 synchronized 關鍵字,該方法會將共享數據 Data.count 自增,而後進行打印輸出。可是,咱們發現輸出的結果顯示,count 居然有重複,這意味着數據出現了髒讀,咱們在打印前,有其餘線程對共享數據再次進行了修改!鎖機制無效,爲何?

由於 synchronized 取得的鎖是對象鎖,而不是把一段代碼或方法當成鎖。這意味着, 要實現同步,即某線程執行,其餘線程等待,前提是多個線程訪問的是同一個對象。而上例中,顯然 counter1 和 counter2 是兩個對象,咱們的鎖也就沒有效果。

咱們試着把兩個線程的 Counter 類統一下,鎖機制就如咱們所願了:
//Test
public class Test {
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1 = new MyThread(counter);
        Thread t2 = new MyThread(counter);
        t1.start();
        t2.start();
    }
}

//輸出結果示例
current count = 1
current count = 2
current count = 3
current count = 4
current count = 5
current count = 6
current count = 7
current count = 8
...

最後,簡單總結:
  • 只對改變共享資源的地方進行同步,而不是全部方法。同步塊越大,多線程的效率也越低
  • synchronized 關鍵字能夠修飾方法和代碼塊,可是不能修飾構造函數、抽象方法、成員變量
  • synchronized 關鍵字沒法繼承
  • synchronized 不論在方法仍是對象上,取得的鎖都是對象,注意搞清楚鎖定的是哪一個對象
相關文章
相關標籤/搜索