併發安全的特性 -- 原子性:鎖

併發安全的特性 – 原子性:鎖

  JDK爲我們提供了兩種我們比較常見的鎖,分別是:

第一種,就是我們比較熟悉的synchronized: 依賴於JVM的關鍵字。

第二種,是Lock: 依賴特殊的CPU指令,代碼實現,ReektrantLock。

原子性:synchronized

主要有以下幾種使用方法:

一、修飾代碼塊

  大括號括起來的代碼,作用於調用的對象。指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。 和 synchronized 方法一樣,synchronized(this)代碼塊也是鎖定當前對象的。synchronized 關鍵字加到 static 靜態方法和synchronized(class)代碼塊上都是是給 Class 類上鎖。這裏再提一下:synchronized關鍵字加到非 static 靜態方法上是給對象實例上鎖。另外需要注意的是:儘量不要使用 synchronized(String a) 因爲JVM中,字符串常量池具有緩衝功能!

二、修飾實例方法

子類繼承該方法時,不能繼承synchronized關鍵字,作用在同一個對象。

private static Object obj = new Object();

    //修飾一個代碼塊(作用在同一個對象)
    public void test1(int j) {
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                log.info("test1 {} - {}", j, i);
            }
        }
    }

    //修飾一個方法(子類繼承該方法時,不能繼承synchronized關鍵字)
    //作用在同一個對象
    public synchronized void test2(int j) {
        for (int i = 0; i < 100; i++) {
            log.info("test2 {} - {}", j, i);
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SynchronizedExample1 example1 = new SynchronizedExample1();
        SynchronizedExample1 example2 = new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
        System.out.println(executorService.submit(() -> {
            example1.test1(1);
        }).get());
        executorService.submit(() -> {
            example1.test1(2);
        });
        executorService.shutdown();
    }

  這個例子的結果是,當同一個對象調用被synchronized修飾的代碼塊或者修飾的實例方法,線程1執行完畢之後,線程2纔可以繼續執行。讀者可以自行更改調用方法或者調用方法的對象,觀察運行結果會有什麼不同。

三、修飾一個類(作用在使用該方法的所有對象)

四、修飾一個靜態方法(作用在使用該方法的所有對象)

子類繼承該方法時,不能繼承synchronized關鍵字,作用於當前類對象加鎖,進入同步代碼前要獲得當前類對象的鎖 也就是給當前類加鎖,會作
用於類的所有對象實例,因爲靜態成員不屬於任何一個實例對象,是類成員( static 表明這是該類的一個靜態資源,不管new了多少個對象,只有一份,所以對該類的所有對象都加了鎖)。所以如果一個線程A調用一個實例對象的非靜態 synchronized 方法,而線程B需要調用這個實例對象所屬類的靜態 synchronized 方法,是允許的,不會發生互斥現象,因爲訪問靜態 synchronized 方法佔用的鎖是當前類的鎖,而訪問非靜態 synchronized 方法佔用的鎖是當前實例對象鎖。

//修飾一個類(作用在使用該方法的所有對象)
    public static void test1(int j) {
        synchronized (SynchronizedExample2.class) {
            for (int i = 0; i < 1000; i++) {
                log.info("test1 {} - {}", j, i);
            }
        }
    }

    //修飾一個靜態方法(子類繼承該方法時,不能繼承synchronized關鍵字)
    //作用在使用該方法的所有對象
    public synchronized static void test2(int j) {
        for (int i = 0; i < 1000; i++) {
            log.info("test2 {} - {}", j, i);
        }
    }

    public static void main(String[] args) {
        SynchronizedExample2 example1 = new SynchronizedExample2();
        SynchronizedExample2 example2 = new SynchronizedExample2();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            example1.test1(1);
        });
        executorService.execute(() -> {
            example2.test1(2);
        });
        executorService.shutdown();
    }

  同樣的這個例子的結果是,當不同對象調用被synchronized修飾的類的方法或者修飾的靜態方法,依然是線程1執行完畢之後,線程2纔可以繼續執行,這就說明這兩種方式是進入同步代碼前要獲得當前類對象的鎖。

關於synchronized和Lock的對比

  synchronized: 是不可能中斷的,適合競爭不是特別激烈的情形,可讀性較好。
  Lock: 可中斷鎖,多樣化同步,競爭激烈時能維持常態。

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