成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

學(xué)妹問我,并發(fā)問題的根源到底是什么?

開發(fā) 開發(fā)工具
原子,在物理學(xué)中定義是組成物體的不可分割的最小的單位。在 java 并發(fā)編程中我們可以將其理解為:一組要么成功要么失敗的操作。

[[408736]]

并發(fā)編程是 java 高級程序員的必備的基礎(chǔ)技能之一。但是想要寫好并發(fā)程序并非易事。

那究竟是什么原因?qū)е麓蟀训?ldquo;格子衫”朋友無法寫出優(yōu)質(zhì)和性能穩(wěn)定的并發(fā)程序呢?根本原因就是大家對并發(fā)編程的核心理論的模糊和不理解。想要運用好一項技術(shù)。理論知識和核心概念是一定要理解透徹的。

今天我們就來一起看下并發(fā)編程三大核心基礎(chǔ)理論:原子性、可見性、有序性

1、原子性

先來看下什么叫原子性

第一種理解:原子(atomic)本意是“不能被進一步分割的最小粒子”,而原子操作(atomic operation)意 為“不可被中斷的一個或一系列操作”

第二種理解:原子性,即一個操作或多個操作,要么全部執(zhí)行并且在執(zhí)行的過程中不被打斷,要么全部不執(zhí)行。(提供了互斥訪問,在同一時刻只有一個線程進行訪問)

原子,在物理學(xué)中定義是組成物體的不可分割的最小的單位。在 java 并發(fā)編程中我們可以將其理解為:一組要么成功要么失敗的操作。

1.1、原子性問題的產(chǎn)生的原因

原子性問題產(chǎn)生的根本原因是什么?我們只要知道了癥狀才能準(zhǔn)確的對癥下藥,本小節(jié),我們就來一起探討下原子性問題的由來。

我們都知道,程序在執(zhí)行的時候,一定是以線程為單位在執(zhí)行的,因為線程是 CPU 進行任務(wù)調(diào)度的基本單位。

電腦的 CPU 會根據(jù)不同的任務(wù)調(diào)度算法去執(zhí)行線程的調(diào)度,將時間分片并派分給各個線程。

當(dāng)某個線程獲得CPU的時間片之后就獲取了CPU的執(zhí)行權(quán),就可以執(zhí)行任務(wù),當(dāng)時間片耗盡之后,就會失去CPU使用權(quán)。

進而本任務(wù)會暫時的停止執(zhí)行。多線程場景下,由于時間片在線程間輪換,就會發(fā)生原子性問題。

看完理論似乎并不能直觀的理解原子性問題。下面我們就通過代碼的方式來具體闡述下原子性問題的產(chǎn)生原因。

1.2、案例分析

我們以常見的 i++ 為例,這是一個老生常談的原子性問題了,先來看下代碼

  1. public class AtomicDemo { 
  2.  
  3.     private int count = 0; 
  4.  
  5.     public void add() { 
  6.  
  7.         count++; 
  8.  
  9.     } 
  10.  
  11.     public int get() { 
  12.  
  13.         return count
  14.  
  15.     } 
  16.  
  17.     public static void main(String[] args) throws InterruptedException { 
  18.  
  19.         CountDownLatch countDownLatch = new CountDownLatch(100); 
  20.  
  21.         AtomicDemo atomicDemo = new AtomicDemo(); 
  22.  
  23.         IntStream.rangeClosed(0, 100).forEach(item -> { 
  24.  
  25.             new Thread(() -> { 
  26.  
  27.                 IntStream.rangeClosed(1, 100).forEach(i -> { 
  28.  
  29.                     atomicDemo.add(); 
  30.  
  31.                 }); 
  32.  
  33.             }).start(); 
  34.  
  35.             countDownLatch.countDown(); 
  36.  
  37.         }); 
  38.  
  39.         countDownLatch.await(); 
  40.  
  41.         System.out.println(atomicDemo.get()); 
  42.  
  43.     } 
  44.  

上面 代碼的作用是將初始值為0的 count 變量,通過100線程每個線程累加100次的方式來累加。想要得到一個結(jié)果為 10000 的值。但是實際上結(jié)果很難達到10000。

產(chǎn)生這個問題的原因:

count++ 的執(zhí)行實際上這個操作不是原子性的,因為 count++ 會被拆分成以下三個步驟執(zhí)行(這樣的步驟不是虛擬的,而是真實情況就是這么執(zhí)行的)

第一步:讀取 count 的值;

第二步:計算 +1 的結(jié)果;

第三步:將 +1 的結(jié)果賦值給 count變量

那問題又來了。分三步又咋樣?讓他執(zhí)行完不就行了?

理論上是這樣子的,大家都很友好,你執(zhí)行完我執(zhí)行,我執(zhí)行完你繼續(xù)。你想象的可能是這樣的”烏托邦圖“

image-20210430131612018

但是實際上這些線程已經(jīng)”黑化”了。他們絕不可能互相謙讓。CPU或者是程序的世界觀里面。大家做任何事情都是在”爭搶“。我們來看下面這張圖:

上圖詳細(xì)分析:

第一步:A線程從主內(nèi)存中讀取 count 的值 0;

第二步:A線程開始對 count 值進行累加;

第三步:B線程從主內(nèi)存中讀取 count 的值 0(PS:具體第三步從哪里開始都不是重點,重點是:A線程將 count 值寫入主內(nèi)存之前 B 線程就開始讀取 count 并執(zhí)行。此時 B線程 讀取到的 count 值依舊是還未被操作過的原始值);

第四步:(PS:到這里其實已經(jīng)不重要了。因為不管 A線程和B線程現(xiàn)在怎么操作。結(jié)果已經(jīng)不可逆轉(zhuǎn),已經(jīng)錯了)B線程開始對 count 值進行累加;

第五步:A 線程將累加后的結(jié)果賦值給 count 結(jié)果為 1;

第六步:B 線程將累加后的結(jié)果賦值給 count 結(jié)果為 1;

第七步:A 線程將結(jié)果 count =1 刷回到主內(nèi)存;

第八步:B 線程將結(jié)果 count =1 刷回到主內(nèi)存;

相信大家此時已經(jīng)非常清晰地分析出了原子性產(chǎn)生的根本原因了。

至于解決方案可以通過鎖或者是 CAS 的方式。具體方案就不再這里贅述了。

2、可見性

萬丈高樓平地起,再復(fù)雜的技術(shù)我們也需要從基本的概念看起來:

可見性:一個線程對共享變量的修改,另外一個線程能夠立刻看到,我們稱為可見性。

2.1、可見性問題產(chǎn)生的原因

在很多年前,那個嫁妝只需要一個手電筒的年代你或許還不會出現(xiàn)可見性這樣的問題,因為大家都是單核處理器,不存在并發(fā)的情況。

而對于現(xiàn)在“視金錢如糞土”的年代。多核處理器已經(jīng)是現(xiàn)代超級計算機的基礎(chǔ)硬件。高速的CPU處理器和緩慢的內(nèi)存之前數(shù)據(jù)的通信成了矛盾。

所以為了解決和緩和這樣的情況,每個CPU和線程都有自己的本地緩存,所謂本地緩存即該緩存僅僅對它所在的處理器可見,CPU緩存與內(nèi)存的數(shù)據(jù)不容易保證一致。

為了避免這種因為寫數(shù)據(jù)速度不一致而導(dǎo)致 CPU 的性能浪費的情況,處理器通過使用寫緩沖區(qū)來臨時保存待寫入主內(nèi)存的數(shù)據(jù)。寫緩沖區(qū)合并對同一內(nèi)存地址的多次寫,并以批處理的方式刷新,也就是說寫緩沖區(qū)不會立即將數(shù)據(jù)刷新到主內(nèi)存中。

緩存不能及時刷新到主內(nèi)存就是導(dǎo)致可見性問題產(chǎn)生的根本原因。

2.2、案例分析

  1. public class AtomicDemo { 
  2.  
  3.     private int count = 0; 
  4.  
  5.     public void add() { 
  6.  
  7.         count++; 
  8.  
  9.     } 
  10.  
  11.     public int get() { 
  12.  
  13.         return count
  14.  
  15.     } 
  16.  
  17.     public static void main(String[] args) throws InterruptedException { 
  18.  
  19.         CountDownLatch countDownLatch = new CountDownLatch(100); 
  20.  
  21.         AtomicDemo atomicDemo = new AtomicDemo(); 
  22.  
  23.         IntStream.rangeClosed(0, 100).forEach(item -> { 
  24.  
  25.             new Thread(() -> { 
  26.  
  27.                 IntStream.rangeClosed(1, 100).forEach(i -> { 
  28.  
  29.                     atomicDemo.add(); 
  30.  
  31.                 }); 
  32.  
  33.             }).start(); 
  34.  
  35.             countDownLatch.countDown(); 
  36.  
  37.         }); 
  38.  
  39.         countDownLatch.await(); 
  40.  
  41.         System.out.println(atomicDemo.get()); 
  42.  
  43.     } 
  44.  

“what * *”,怎么和上面代碼一樣。。。結(jié)果就不截圖了,必然不是10000。

我們來看下執(zhí)行的流程圖(PS:不要糾結(jié)于為什么和上面的不一樣,特定問題特定分析。在闡述一種問題的時候,一定會在某些層面上屏蔽另外一種問題的干擾)

假設(shè) A 線程和 B 線程同時開始執(zhí)行,首先 A 線程和 B 線程會將主內(nèi)存中的 count 的值加載/緩存到自己的本地內(nèi)存中。然后會讀取各自的內(nèi)存中的值去執(zhí)行操作,也就是說此時 A 線程和 B 線程就好像是兩個世界的人,彼此不會產(chǎn)生任何關(guān)聯(lián)。

操作完之后 A 線程將結(jié)果寫回到自己的本地內(nèi)存中,同樣 B 線程將結(jié)果寫回到自己的本地內(nèi)存中。然后回來某個時機各自將結(jié)果刷回到主內(nèi)存。那最終必然是一方的數(shù)據(jù)被另一方覆蓋。這就是緩存的可見性問題。

3、有序性

不積跬步無以至千里,我們還是先來看概念

有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

這有啥的,程序老老實實按照程序員寫的代碼執(zhí)行就完事了,這還會有什么問題嗎?

3.1、有序性問題產(chǎn)生的原因

實際上編譯器為了提高程序執(zhí)行的性能。會改變我們代碼的執(zhí)行順序的。即你寫在前面的代碼不一定是先被執(zhí)行完的。

例如:int a = 1;int b =4;從表面和常規(guī)角度來看,程序的執(zhí)行應(yīng)該是先初始化 a ,然后初始化 b 。但是實際上非常有可能是先初始化 b,然后初始化 a。因為在編譯器看了來,先初始化誰對這兩個變量不會有任何影響。即這兩個變量之間沒有任何的數(shù)據(jù)依賴。

指令重排序有三種類型,分別為:

① 編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。

② 指令級并行的重排序。現(xiàn)代處理器采用了指令級并行技術(shù)(Instruction-Level Parallelism,ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng) 機器指令的執(zhí)行順序。

③ 內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上 去可能是在亂序執(zhí)行。

3.2、案例分析

有序性的案例最常見的就是 DCL了(double check lock)就是單例模式中的雙重檢查鎖功能。先來看下代碼

  1. public class SingletonDclDemo { 
  2.  
  3.     private SingletonDclDemo(){} 
  4.  
  5.     private static SingletonDclDemo instance; 
  6.  
  7.     public static SingletonDclDemo getInstance(){ 
  8.  
  9.         if (Objects.isNull(instance)) { 
  10.  
  11.             synchronized (SingletonDclDemo.class) { 
  12.  
  13.                 if (Objects.isNull(instance)) { 
  14.  
  15.                     instance = new SingletonDclDemo(); 
  16.  
  17.                 } 
  18.  
  19.             } 
  20.  
  21.         } 
  22.  
  23.         return instance; 
  24.  
  25.     } 
  26.  
  27.     public static void main(String[] args) { 
  28.  
  29.         IntStream.rangeClosed(0,100).forEach(item->{ 
  30.  
  31.             new Thread(SingletonDclDemo::getInstance).start(); 
  32.  
  33.         }); 
  34.  
  35.     } 
  36.  

這個代碼還是比較簡單的。

在獲取對象實例的方法中,程序首先判斷 instance 對象是否為空,如果為空,則鎖定SingletonDclDemo.class 并再次檢查instance是否為空,如果還為空則創(chuàng)建 Singleton的一個實例。看似很完美,既保證了線程完全的初始化單例,又經(jīng)過判斷 instance 為 null 時再用 synchronized 同步加鎖。但是還有問題!

instance = new SingletonDclDemo(); 創(chuàng)建對象的代碼,分為三步:① 分配內(nèi)存空間;② 初始化對象SingletonDclDemo;③ 將內(nèi)存空間的地址賦值給instance;

但是這三步經(jīng)過重排之后:① 分配內(nèi)存空間 ② 將內(nèi)存空間的地址賦值給instance ③ 初始化對象SingletonDclDemo

會導(dǎo)致什么結(jié)果呢?

線程 A 先執(zhí)行 getInstance() 方法,當(dāng)執(zhí)行完指令②時恰好發(fā)生了線程切換,切換到了線程B上;如果此時線程B也執(zhí)行 getInstance() 方法,那么線程B在執(zhí)行第一個判斷時會發(fā)現(xiàn)instance!=null,所以直接返回instance,而此時的instance是沒有初始化過的,如果我們這個時候訪問instance的成員變量就可能觸發(fā)空指針異常。

繼續(xù)來張圖來更直觀的理解下:

具體的執(zhí)行流程在上面已經(jīng)分析了。相信這張圖片一定能讓你徹底理解。

4、本文小結(jié)

 

并發(fā)編程的學(xué)習(xí)和使用并非一朝一夕的事情,也并非會幾個理論就能寫好優(yōu)質(zhì)的并發(fā)程序。這需要長時間的實踐和總結(jié)。好的代碼很少是寫出來的,都是迭代和優(yōu)化的。

 

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2011-04-27 09:30:48

企業(yè)架構(gòu)

2020-03-05 10:28:19

MySQLMRR磁盤讀

2022-10-08 00:00:00

Spring數(shù)據(jù)庫項目

2020-09-27 06:53:57

MavenCDNwrapper

2020-10-14 06:22:14

UWB技術(shù)感知

2020-09-22 08:22:28

快充

2010-11-01 01:25:36

Windows NT

2009-06-09 22:11:44

JavaScriptObject

2013-06-09 09:47:31

.NetPDBPDB文件

2010-04-22 14:14:29

Live-USB

2019-10-30 10:13:15

區(qū)塊鏈技術(shù)支付寶

2021-09-03 09:12:09

Linux中斷軟件

2020-08-04 14:20:20

數(shù)據(jù)湖Hadoop數(shù)據(jù)倉庫

2023-10-11 08:29:54

volatileJava原子性

2021-01-21 21:24:34

DevOps開發(fā)工具

2021-07-07 05:07:15

JDKIterator迭代器

2023-07-12 15:32:49

人工智能AI

2024-02-04 00:01:00

云原生技術(shù)容器

2021-09-01 23:29:37

Golang語言gRPC

2021-02-05 10:03:31

區(qū)塊鏈技術(shù)智能
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 亚洲社区在线 | 成人在线精品视频 | 在线观看视频h | 中文字幕亚洲一区二区三区 | 国产日韩精品一区二区 | 九色国产| 亚洲在线视频 | 蜜桃一区二区三区在线 | 亚洲人成网站777色婷婷 | 久久国产精品免费一区二区三区 | 精品视频www| 国产欧美一区二区三区另类精品 | 久久久久久久久淑女av国产精品 | 亚洲69p | 国产精品久久福利 | 精品视频在线观看 | 亚洲a人| 成人国产精品免费观看视频 | 亚洲精品1区| 高清免费在线 | 国产精品视频不卡 | 成年人黄色一级片 | 在线午夜 | 日日综合 | 国产日韩av一区二区 | 做a视频 | 国产精品视频网站 | 黄色一级视频 | 日韩成人av在线 | 中文字幕日韩欧美一区二区三区 | 精品一区二区三区在线观看国产 | 欧美日韩国产传媒 | 欧美午夜精品 | 国产精品日产欧美久久久久 | 福利视频网站 | 国产蜜臀97一区二区三区 | 欧州一区二区三区 | 久久91av| 91.com视频 | 久久99精品久久久久子伦 | 国产成人亚洲精品 |