執行緒安全性-原子性之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效能好; 缺點就是隻能同步一個值;