執行緒安全性-原子性、可見性、有序性
執行緒安全性
- 當多個執行緒訪問某個類時,不管執行時環境採用何種排程方式或者這些執行緒將如何交替執行,並且在主調程式碼中不需要任何額外的同步或協同,這個類都能表現出正確的行為,那麼這個類就是執行緒安全的。
- 原子性:提供了互斥訪問,同一時刻只能有一個執行緒來對它進行操作
- 可見性:一個執行緒對主記憶體的修改可以及時被其他執行緒觀察到
- 有序性:一個執行緒觀察其他執行緒中的指令執行順序,由於指令重排序的存在,該觀察結果一般雜亂無序
原子性
Atomic包
這裡使用AtomicInteger進行計數,Java底層是使用CAS進行的悲觀鎖的同步。
詳解CAS: https://blog.csdn.net/v123411739/article/details/79561458
Java中的CAS: https://blog.csdn.net/mmoren/article/details/79185862
上文提到的CAS都有三個運算元,記憶體位置(V)、預期原值(A)和新值(B)。 如果記憶體位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值 。否則,處理器不做任何操作。我再理解這個概念時遇到了一個問題,在多執行緒的情況下,java如何知道預期原值。這實際上和之前的JVM記憶體模型有關。
一個執行緒間共享的變數,首先在主存中會保留一份,然後每個執行緒的工作記憶體也會保留一份副本。這裡說的預期值,就是執行緒保留的副本。當該執行緒從主存中獲取該變數的值後,主存中該變數可能已經被其他執行緒重新整理了,但是該執行緒工作記憶體中該變數卻還是原來的值,這就是所謂的預期值了。當你要用CAS重新整理該值的時候,如果發現執行緒工作記憶體和主存中不一致了,就會失敗,如果一致,就可以更新成功。
package com.ice.concurrency.example.count;
import com.ice.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@ThreadSafe
public class CountExample2 {
public static int clientTotal = 5000;
public static int threadTotal = 200;
public static AtomicInteger count = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i = 0;i<clientTotal;i++){
executorService.execute(()->{
try {
semaphore.acquire();
add();
semaphore.release();
} catch (InterruptedException e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}",count.get());
}
private static void add(){
count.incrementAndGet();
}
}
實際上Java底層到底是如何實現CAS的,我還不是十分清楚。但目前而言,已經大致理解了CAS的原理和優缺點即可。
AtomicReferce類
@Slf4j
@ThreadSafe
public class AtomicExample4 {
private static AtomicReference<Integer> count = new AtomicReference<>(0);
public static void main(String[] args) {
count.compareAndSet(0,2); // 2
count.compareAndSet(0,1); // no
count.compareAndSet(1,3); // no
count.compareAndSet(2,4); // 4
count.compareAndSet(3,5); // no
log.info("count:{}",count.get());
}
}
>>> count:4
AtomicIntegerFieldUpdater類
@Slf4j
@ThreadSafe
public class AtomicExample5 {
private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class,"count");
// count欄位必須為volatile修飾且非static才能被AtomicIntegerFieldUpdater改變。
@Getter
public volatile int count=100;
private static AtomicExample5 example5 = new AtomicExample5();
public static void main(String[] args) {
if(updater.compareAndSet(example5,100,120)){
log.info("update success, {}",example5.getCount());
}
if(updater.compareAndSet(example5,100,120)){
log.info("update success, {}",example5.getCount());
}else{
log.info("update failed, {}",example5.getCount());
}
}
}
AtomicStampReference類(解決ABA問題)
AtomicLongArray類
AtomicBoolean類
@Slf4j
@ThreadSafe
public class AtomicExample6 {
public static int clientTotal = 5000;
public static int threadTotal = 200;
public static AtomicBoolean count = new AtomicBoolean(false);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i = 0;i<clientTotal;i++){
executorService.execute(()->{
try {
semaphore.acquire();
test();
semaphore.release();
} catch (InterruptedException e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("isHappened:{}",count.get());
}
private static void test(){
if(count.compareAndSet(false,true)){
log.info("execute");
}
}
}
執行這段程式碼,可以看到log只列印了一次execute。因為不論多少個執行緒同時訪問AtomicBoolean,只有一個能成功修改它的值。
鎖
sychronized關鍵字
package com.ice.concurrency.example.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class SynchronizedExample1 {
public void test(){
for(int i = 0;i<10;i++){
log.info("test - {}",i);
}
}
// synchronized修飾一個程式碼塊
public void test1(int j){
synchronized (this){
for(int i = 0;i<10;i++){
log.info("test1 {} - {}",j,i);
}
}
}
// 修飾一個方法
public synchronized void test2(int j){
for(int i = 0;i<10;i++){
log.info("test2 {} - {}",j,i);
}
}
// 修飾一個類
public static void test3(int j){
synchronized (SynchronizedExample1.class){
for(int i = 0;i<10;i++){
log.info("test3 {} - {}",j,i);
}
}
}
// 修飾一個靜態方法
public static synchronized void test4(int j){
for(int i = 0;i<10;i++){
log.info("test4 {} - {}",j,i);
}
}
public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
example1.test1(1);
});
executorService.execute(()->{
example2.test1(2);
});
}
}
可見性
可見性:一個執行緒對主記憶體的修改可以及時被其他執行緒觀察到
@Slf4j
@NotThreadSafe
public class CountExample4 {
public static int clientTotal = 5000;
public static int threadTotal = 200;
public static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i = 0;i<clientTotal;i++){
executorService.execute(()->{
try {
semaphore.acquire();
add();
semaphore.release();
} catch (InterruptedException e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}",count);
}
private static void add(){
// 雖然這裡count變數被volatile修飾,是可見的,但依然沒有原子性,執行緒不安全
// 當兩個執行緒同時操作count時,同時讀一個數,又同時重新整理進記憶體,依然會浪費一次操作
count++;
}
}
volatile的應用
雖然volatile修飾的變數時刻讀取都是他的真實值,因此特別適合用於作為標示量。
有序性
volatile可以保證一定的有序性
synchronized,lock保證了單執行緒的執行,因此肯定時有序的
java記憶體模型具有一些先天的有序性(不需要通過任何手段就能得到的有序性,即happens-before原則)
happens-before原則
happens-before規則如下:
- 程式順序規則:一個執行緒中的每個操作,happens- before 於該執行緒中的任意後續操作。
- 監視器鎖規則:對一個監視器鎖的解鎖,happens- before 於隨後對這個監視器鎖的加鎖。
- volatile變數規則:對一個volatile域的寫,happens- before 於任意後續對這個volatile域的讀。
- 傳遞性:如果A happens- before B,且B happens- before C,那麼A happens- before C。
- Thread.start()的呼叫會happens-before於啟動執行緒裡面的動作。
- Thread中的所有動作都happens-before於其他執行緒從Thread.join中成功返回。
如果兩個操作執行的次序無法從happens-before原則中推匯出來,那麼就不能保證他們的有序性,jvm就可以隨意的對他們進行重排序
相關文章
- java多執行緒3:原子性,可見性,有序性Java執行緒
- 你還不懂可見性、有序性和原子性?
- Java併發之原子性、有序性、可見性Java
- 三大性質總結:原子性、可見性以及有序性
- 深入理解Java多執行緒與併發框(第③篇)——Java記憶體模型與原子性、可見性、有序性Java執行緒記憶體模型
- [深入理解Java虛擬機器]原子性/可見性/有序性Java虛擬機
- 高階java必須清楚的概念:原子性、可見性、有序性Java
- volatile,可見性,有序性
- 執行緒安全性執行緒
- volatile 可見性與原子性
- Java併發程式設計Bug源頭:可見性、原子性和有序性問題Java程式設計
- 二、執行緒安全性執行緒
- 「跬步千里」詳解 Java 記憶體模型與原子性、可見性、有序性Java記憶體模型
- 02. 執行緒安全性執行緒
- 走進volatile的世界,探索它與可見性,有序性,原子性之間的愛恨情仇!
- 可見性有序性,Happens-before來搞定APP
- 從硬體級別再看可見性和有序性
- Java併發程式設計-併發程式設計的Bug源頭:可見性、原子性和有序性問題Java程式設計
- 多執行緒的安全性問題(三)執行緒
- java安全編碼指南之:可見性和原子性Java
- 執行緒安全性保證---JMM特性詳解執行緒
- 多執行緒安全性和Java中的鎖執行緒Java
- JCIP閱讀筆記之執行緒安全性筆記執行緒
- 深入解讀HashMap執行緒安全性問題HashMap執行緒
- 【重學Java】多執行緒進階(執行緒池、原子性、併發工具類)Java執行緒
- 探究Spring中Bean的執行緒安全性問題SpringBean執行緒
- Java併發程式設計-解決可見性與有序性問題Java程式設計
- 你知道Java是如何解決可見性和有序性問題的嗎?Java
- Java核心知識體系7:執行緒安全性討論Java執行緒
- volatile型變數語義講解一 :對所有執行緒的可見性變數執行緒
- 深入理解std::shared_ptr:原理、用法及其執行緒安全性執行緒
- Volatile如何保證執行緒可見性之匯流排鎖、快取一致性協議執行緒快取協議
- 通過可見性逆轉物聯網安全性的7款工具
- 記一次對Java多執行緒記憶體可見性的測試Java執行緒記憶體
- 執行緒屬性設定執行緒
- 第10章:併發和分散式程式設計 10.1併發性和執行緒安全性分散式程式設計執行緒
- Java核心知識體系8:Java如何保證執行緒安全性Java執行緒
- Java中列舉的執行緒安全性及序列化問題Java執行緒