1. 程式人生 > 其它 >執行緒安全性-原子性之synchronized鎖

執行緒安全性-原子性之synchronized鎖

原子性提供了互斥訪問:同一時刻只能有一個執行緒進行操作;

除了Atomic包類之外,還有鎖可以實現此功能;

synchronized:  java關鍵字,依賴於jvm實現鎖功能,被此關鍵字所修飾的,都是在同一時刻,只能有一個執行緒操作;

Lock: 由jdk提供的鎖,Lock類,比如ReentranLock等..;

這次針對synchronized進行介紹:synchronized是一種同步鎖,修飾物件有四種;

    一,修飾程式碼塊:大括號括起來的程式碼,作用於呼叫的物件,被修飾的程式碼稱為同步語句塊

    二,整個方法,作用於呼叫的物件,被修飾的方法稱為同步方法

    三,整個靜態方法,作用於所有物件

    四,修飾類,synchronized後面括號括起來的部分,作用於所有物件

package com.example.concurrency.example.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author xiaozhuang
 * @date 2022年04月08日 14:47
 */
@Slf4j
public class SynchronizedExample1 {

    // 修飾程式碼塊 作用於呼叫的物件
    public void test1(){
        synchronized (this){
            for (int i = 0; i < 3; i++) {
                log.info("修飾程式碼塊:{}",i);
            }
        }
    }
    public static void main(String[] args) {
        SynchronizedExample1 synchronizedExample1=new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(()->{
            synchronizedExample1.test1();
        });
        executorService.execute(()->{
            synchronizedExample1.test1();
        });
         executorService.shutdown();
    }
}

先看看修飾程式碼塊的例子,它是作用於呼叫的物件,這裡我們根據物件 線上程池中呼叫了2次,test1方法,看一下輸出

再改為兩個物件呼叫時

發現它是交替按順序輸出的,它是作用於呼叫物件,所以不用物件呼叫之間是相互不影響的,在上面一個物件呼叫兩次test1方法,則是需要一個執行完再執行另一個。

再看看修飾方法,也是作用於呼叫物件:

再看看兩個物件呼叫時

可以看到輸出是與上面的修飾程式碼塊是一樣;

可以得出一個結論:如果一個方法內部一整個都是同步程式碼塊,那麼它與用synchronized修飾的方法是等同的。

如果當前類是父類,當子類繼承父類時,呼叫父類被synchronized修飾的方法時,是不會帶上synchronized關鍵字的,需要自己手動加上去,synchronized不屬於方法宣告部分;

再看看修飾靜態方法與修飾類的:

這裡因為修飾類與修飾靜態方法的作用於所有的物件,所以在這個類裡,都是需要逐個執行完畢;

結論:如果一個方法裡,一整個都是被synchronized所修飾的類包圍的時候,那麼它與被synchronized修飾的靜態方法的作用是等同的。

通過上面對synchronized的瞭解,所以一個計數的問題也就迎刃而解了;

@Slf4j
public class ConcurrencyTest {
    // 請求訪問總數
    public static int clientTotal = 5000;
    // 同時併發執行的執行緒數
    public static int threadTotal = 200;
    // 計數的值
    public static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        // 執行緒池
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 訊號量  引數為  執行併發的數目
        final Semaphore semaphore = new Semaphore(threadTotal);
        // 遞減計數器  引數為 請求數量  沒執行成功一次會  減1
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            // 講請求放入執行緒池中
            executorService.execute(() -> {
                try {
                    //判斷訊號量 判斷當前執行緒是否允許被執行
                    semaphore.acquire();
                    add();
                    // 釋放程序
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                // 沒執行完一次 計算器-1
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        // 關閉執行緒池
        executorService.shutdown();
        log.info("count:{}", count);
    }
    private static void add() {
        count++;
    }
}

只需要在add()方法加上synchronized,就會變成執行緒安全了;

最後對比一下synchronized,Lock,Atomic三者的區別

synchronized:在執行到synchronized作用範圍內的時候,它是不可中斷的,必須等待程式碼執行完畢。適合競爭不激烈,可讀性好,競爭激烈時,效能下降較快;

Lock:是可以中斷的,只需要呼叫unLock就行,多樣化同步,競爭激烈時能維持常態;

Atomic: 競爭激烈時能維持常態,比Lock效能好;  缺點就是隻能同步一個值;