偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

詭異的并發(fā)之可見(jiàn)性

開(kāi)發(fā) 后端
隨著祖國(guó)越來(lái)越繁榮昌盛,隨著科技的進(jìn)步,設(shè)備的更新?lián)Q代,計(jì)算機(jī)體系結(jié)構(gòu)、操作系統(tǒng)、編譯程序都在不斷地改革創(chuàng)新,但始終有一點(diǎn)是不變的:那就是下面三者的性能耗時(shí):CPU < 內(nèi)存 < I/O

 我們都知道,隨著祖國(guó)越來(lái)越繁榮昌盛,隨著科技的進(jìn)步,設(shè)備的更新?lián)Q代,計(jì)算機(jī)體系結(jié)構(gòu)、操作系統(tǒng)、編譯程序都在不斷地改革創(chuàng)新,但始終有一點(diǎn)是不變的:那就是下面三者的性能耗時(shí):CPU < 內(nèi)存 < I/O

但也正因?yàn)檫@些改變,也就在并發(fā)程序中出現(xiàn)了一些詭異的問(wèn)題,而其中最昭著的三大問(wèn)題就是:可見(jiàn)性、有序性、原子性。

而今天阿粉我就為大家介紹其中的惡霸之一可見(jiàn)性。

01 可見(jiàn)性的闡述

可見(jiàn)性 的定義是:一個(gè)線程對(duì)共享變量的修改,另外一個(gè)線程能夠立刻看到。

在單核時(shí)代,所有線程都在一個(gè)CPU上執(zhí)行,所以一個(gè)線程的寫,一定是對(duì)其它線程可見(jiàn)的。就好比,一個(gè)總經(jīng)理下面就一個(gè)項(xiàng)目負(fù)責(zé)人。

 

 

 

 

此時(shí),項(xiàng)目經(jīng)理查看到任務(wù)G后,分配給員工A和員工B,那么這個(gè)任務(wù)的進(jìn)度就能隨時(shí)掌握在項(xiàng)目經(jīng)理手中了;每個(gè)員工都能從項(xiàng)目經(jīng)理處得知最新的項(xiàng)目進(jìn)度。

而在多核時(shí)代后,每個(gè)CPU都有自己的緩存,這就出現(xiàn)了可見(jiàn)性問(wèn)題。

 

 

 

 

此時(shí),兩個(gè)項(xiàng)目經(jīng)理同時(shí)查看到任務(wù)G后,各自分配給自己下屬員工,那么這個(gè)任務(wù)的進(jìn)度就只能掌握在各自項(xiàng)目經(jīng)理手中了,因?yàn)樗袉T工的工作進(jìn)度并不是匯報(bào)給同一個(gè)項(xiàng)目經(jīng)理;那么,每個(gè)員工只能得知自己項(xiàng)目組員工的工作進(jìn)度,并不能得知其他項(xiàng)目組的工作進(jìn)度。所以,當(dāng)多個(gè)項(xiàng)目經(jīng)理在做同一個(gè)任務(wù)時(shí),就可能出現(xiàn)任務(wù)配比不均、任務(wù)進(jìn)度拖延、任務(wù)重復(fù)進(jìn)行等多種問(wèn)題。

總和上面的例子來(lái)講,就是因?yàn)檫M(jìn)度的不及時(shí)更新,導(dǎo)致數(shù)據(jù)不是最新,導(dǎo)致決策失誤。所以,我們隱約可以看出,內(nèi)存并不直接與Cpu打交道,而是通過(guò)高速緩存與Cpu打交道。

cpu <——> 高速緩存 <———> 內(nèi)存

通過(guò)一張圖片來(lái)表示就是(多核):

 

 

 

 

下文我們的闡述,若無(wú)特殊說(shuō)明,都是基于多核的。

02 原因分析

可見(jiàn)性問(wèn)題都是由Cpu緩存不一致為并發(fā)編程帶來(lái),而其中的主要有下面三種情況:

2.1、線程交叉執(zhí)行

線程交叉執(zhí)行多數(shù)情況是由于線程切換導(dǎo)致的,例如下圖中的線程A在執(zhí)行過(guò)程中切換到線程B執(zhí)行完成后,再切換回線程A執(zhí)行剩下的操作;此時(shí)線程B對(duì)變量的修改不能對(duì)線程A立即可見(jiàn),這就導(dǎo)致了計(jì)算結(jié)果和理想結(jié)果不一致的情況。

 

 

 

 

2.2、重排序結(jié)合線程交叉執(zhí)行

例如下面這段代碼

 

  1. int a = 0;    //行1 
  2.    int b = 0;    //行2 
  3.    a = b + 10;   //行3 
  4.    b = a + 9;    //行4 

如果行1和行2在編譯的時(shí)候改變順序,執(zhí)行結(jié)果不會(huì)受到影響;

如果將行3和行4在變異的時(shí)候交換順序,執(zhí)行結(jié)果就會(huì)受到影響,因?yàn)閎的值得不到預(yù)期的19;

 

 

 

 

由圖知:由于編譯時(shí)改變了執(zhí)行順序,導(dǎo)致結(jié)果不一致;而兩個(gè)線程的交叉執(zhí)行又導(dǎo)致線程改變后的結(jié)果也不是預(yù)期值,簡(jiǎn)直雪上加霜!

2.3、共享變量更新后的值沒(méi)有在工作內(nèi)存及主存間及時(shí)更新

因?yàn)橹骶€程對(duì)共享變量的修改沒(méi)有及時(shí)更新,子線程中不能立即得到最新值,導(dǎo)致程序不能按照預(yù)期結(jié)果執(zhí)行。

例如下面這段代碼:

 

  1. package com.itquan.service.share.resources.controller; 
  2.  
  3. import java.time.LocalDateTime; 
  4.  
  5. /** 
  6.  * @author :mmzsblog 
  7.  * @description:共享變量在線程間的可見(jiàn)性測(cè)試 
  8.  */ 
  9. public class VisibilityDemo { 
  10.  
  11.     // 狀態(tài)標(biāo)識(shí)flag 
  12.     private static boolean flag = true
  13.  
  14.     public static void main(String[] args) throws InterruptedException { 
  15.         System.out.println(LocalDateTime.now() + "主線程啟動(dòng)計(jì)數(shù)子線程"); 
  16.         new CountThread().start(); 
  17.  
  18.         Thread.sleep(1000); 
  19.         // 設(shè)置flag為false,使上面啟動(dòng)的子線程跳出while循環(huán),結(jié)束運(yùn)行 
  20.         VisibilityDemo.flag = false
  21.         System.out.println(LocalDateTime.now() + "主線程將狀態(tài)標(biāo)識(shí)flag被置為false了"); 
  22.     } 
  23.  
  24.     static class CountThread extends Thread { 
  25.         @Override 
  26.         public void run() { 
  27.             System.out.println(LocalDateTime.now() + "計(jì)數(shù)子線程start計(jì)數(shù)"); 
  28.             int i = 0; 
  29.             while (VisibilityDemo.flag) { 
  30.                 i++; 
  31.             } 
  32.             System.out.println(LocalDateTime.now() + "計(jì)數(shù)子線程end計(jì)數(shù),運(yùn)行結(jié)束:i的值是" + i); 
  33.         } 
  34.     } 
  35.  

運(yùn)行結(jié)果是:

 

 

 

 

從控制臺(tái)的打印結(jié)果可以看出,因?yàn)橹骶€程對(duì)flag的修改,對(duì)計(jì)數(shù)子線程沒(méi)有立即可見(jiàn),所以導(dǎo)致了計(jì)數(shù)子線程久久不能跳出while循環(huán),結(jié)束子線程。

對(duì)于這種情況,作為有強(qiáng)迫癥的阿粉我當(dāng)然不能忍,所以就引出了下一個(gè)問(wèn)題:如何解決線程間不可見(jiàn)性

03 如何解決線程間不可見(jiàn)性

為了保證線程間可見(jiàn)性我們一般有3種選擇:

3.1、volatile:只保證可見(jiàn)性

volatile關(guān)鍵字能保證可見(jiàn)性,但也只能保證可見(jiàn)性,在此處就能保證flag的修改能立即被計(jì)數(shù)子線程獲取到。

此時(shí)糾正上面例子出現(xiàn)的問(wèn)題,只需在定義全局變量的時(shí)候加上volatile關(guān)鍵字

// 狀態(tài)標(biāo)識(shí)flag

private static volatile boolean flag = true;

3.2、Atomic相關(guān)類:保證可見(jiàn)性和原子性

將標(biāo)識(shí)狀態(tài)flag在定義的時(shí)候使用Atomic相關(guān)類來(lái)進(jìn)行定義的話,就能很好的保證flag屬性的可見(jiàn)性以及原子性。

此時(shí)糾正上面例子出現(xiàn)的問(wèn)題,只需在定義全局變量的時(shí)候?qū)⒆兞慷x成Atomic相關(guān)類

// 狀態(tài)標(biāo)識(shí)flag

private static AtomicBoolean flag = new AtomicBoolean(true);

不過(guò)值得注意的一點(diǎn)是,此時(shí)原子類相關(guān)的方法設(shè)置新值和得到值的放的是有點(diǎn)變化,如下:

 

  1. // 設(shè)置flag的值 
  2.     VisibilityDemo.flag.set(false); 
  3.      
  4.     // 獲取flag的值 
  5.     VisibilityDemo.flag.get() 

3.3、Lock: 保證可見(jiàn)性和原子性

此處我們使用的是Java常見(jiàn)的synchronized關(guān)鍵字。

此時(shí)糾正上面例子出現(xiàn)的問(wèn)題,只需在為計(jì)數(shù)操作i++添加synchronized關(guān)鍵字修飾

 

  1. synchronized (this) { 
  2.         i++; 
  3.     } 

通過(guò)上面三種方式,阿粉我都得到類似如下的期望結(jié)果:

 

 

 

 

然而,接下來(lái)阿粉我要對(duì)其中的volatile和synchronized關(guān)鍵字做一番較為詳細(xì)的解釋。

04 可見(jiàn)性-volatile

Java內(nèi)存模型對(duì)volatile關(guān)鍵字定義了一些特殊的訪問(wèn)規(guī)則,當(dāng)一個(gè)變量被volatile修飾后,它將具備兩種特性,或者說(shuō)volatile具有下列兩層語(yǔ)義:

  • 第一、保證了不同線程對(duì)這個(gè)變量進(jìn)行讀取時(shí)的可見(jiàn)性。即一個(gè)線程修改了某個(gè)變量的值, 這個(gè)新值對(duì)其他線程來(lái)說(shuō)是立即可見(jiàn)的。(volatile解決了線程間共享變量的可見(jiàn)性問(wèn)題)。
  • 第二、禁止進(jìn)行指令重排序, 阻止編譯器對(duì)代碼的優(yōu)化。

針對(duì)第一點(diǎn),volatile保證了不同線程對(duì)這個(gè)變量進(jìn)行讀取時(shí)的可見(jiàn)性,具體表現(xiàn)為:

  • 1:使用 volatile 關(guān)鍵字會(huì)強(qiáng)制將在某個(gè)線程中修改的共享變量的值立即寫入主內(nèi)存。
  • 2:使用 volatile 關(guān)鍵字的話, 當(dāng)線程 2 進(jìn)行修改時(shí), 會(huì)導(dǎo)致線程 1 的工作內(nèi)存中變量的緩存行無(wú)效(反映到硬件層的話, 就是 CPU 的 L1或者 L2 緩存中對(duì)應(yīng)的緩存行無(wú)效);

附一張CPU緩存模型圖:

 

 

 

 

  • 3:由于線程 1 的工作內(nèi)存中變量的緩存行無(wú)效,所以線程1再次讀取變量的值時(shí)會(huì)去主存讀取?;谶@一點(diǎn),所以我們經(jīng)常會(huì)看到文章中或者書本中會(huì)說(shuō)volatile 能夠保證可見(jiàn)性。

綜上所述:就是用volatile修飾的變量,對(duì)這個(gè)變量的讀寫,不能使用 CPU 緩存,必須從內(nèi)存中讀取或者寫入。

使用volatile無(wú)法保障線程安全,那么volatile的作用是什么呢?

其中之一:(對(duì)狀態(tài)量進(jìn)行標(biāo)記,保證其它線程看到的狀態(tài)量是最新值)

 

 

 

 

volatile關(guān)鍵字是Java虛擬機(jī)提供的最輕量級(jí)的同步機(jī)制,很多人由于對(duì)它理解不夠(其實(shí)這里你想理解透的話可以看看happens-before原則),而往往更愿意使用synchronized來(lái)做同步。所以接下來(lái)阿粉我再說(shuō)說(shuō)synchronized關(guān)鍵字。

05 可見(jiàn)性synchronized

 

 

 

5.1、作用域

synchronized關(guān)鍵字的作用域有二種:

  • 1)是某個(gè)對(duì)象實(shí)例內(nèi),synchronized aMethod(){}可以防止多個(gè)線程同時(shí)訪問(wèn)這個(gè)對(duì)象的synchronized方法。

如果一個(gè)對(duì)象有多個(gè)synchronized方法,只要一個(gè)線程訪問(wèn)了其中的一個(gè)synchronized方法,其它線程不能同時(shí)訪問(wèn)這個(gè)對(duì)象中任何一個(gè)synchronized方法。

這時(shí),不同的對(duì)象實(shí)例的synchronized方法是不相干擾的。也就是說(shuō),其它線程照樣可以同時(shí)訪問(wèn)相同類的另一個(gè)對(duì)象實(shí)例中的synchronized方法。

因?yàn)楫?dāng)修飾非靜態(tài)方法的時(shí)候,鎖定的是當(dāng)前實(shí)例對(duì)象。

  • 2)是某個(gè)類的范圍,synchronized static aStaticMethod{}防止多個(gè)線程同時(shí)訪問(wèn)這個(gè)類中的synchronized static 方法。它可以對(duì)類的所有對(duì)象實(shí)例起作用。

因?yàn)楫?dāng)修飾靜態(tài)方法的時(shí)候,鎖定的是當(dāng)前類的 Class 對(duì)象。

5.2、可用于方法中的某個(gè)區(qū)塊中

除了方法前用synchronized關(guān)鍵字,synchronized關(guān)鍵字還可以用于方法中的某個(gè)區(qū)塊中,表示只對(duì)這個(gè)區(qū)塊的資源實(shí)行互斥訪問(wèn)。

用法是:

 

  1. synchronized(this){ 
  2.     /*區(qū)塊*/ 

它的作用域是當(dāng)前對(duì)象;

5.3、不能繼承

synchronized關(guān)鍵字是不能繼承的,也就是說(shuō),基類的方法

 

  1. synchronized f(){ 
  2.     // 具體操作 

在繼承類中并不自動(dòng)是

 

  1. synchronized f(){ 
  2.     // 具體操作 

而是變成了

 

  1. f(){ 
  2.     // 具體操作 

繼承類需要你顯式的指定它的某個(gè)方法為synchronized方法;

綜上3點(diǎn)所述:synchronized關(guān)鍵字主要有以下這3種用法:

  • 修飾實(shí)例方法:作用于當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖
  • 修飾靜態(tài)方法:作用于當(dāng)前類對(duì)象加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類對(duì)象的鎖
  • 修飾代碼塊:指定加鎖對(duì)象,對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼塊前要獲得給定對(duì)象的鎖

這三種用法就基本保證了共享變量在讀取的時(shí)候,讀取到的是最新的值。

5.4、JVM關(guān)于synchronized的兩條規(guī)定

  • 線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存
  • 線程加鎖時(shí),將清空工作內(nèi)存中共享變量的值,從而是使用共享變量時(shí),需要從主內(nèi)存中重新讀取最新的值(注意:加鎖與解鎖是同一把鎖)

從上面的這兩條規(guī)則也可以看出,這種方式保證了內(nèi)存中的共享變量一定是最新值。

但我們?cè)谑褂胹ynchronized保證可見(jiàn)性的時(shí)候也要注意以下幾點(diǎn):

  • A.無(wú)論synchronized關(guān)鍵字加在方法上還是對(duì)象上,它取得的鎖都是對(duì)象;而不是把一段代碼或函數(shù)當(dāng)作鎖――而且同步方法很可能還會(huì)被其他線程的對(duì)象訪問(wèn)。
  • B.每個(gè)對(duì)象只有一個(gè)鎖(lock)與之相關(guān)聯(lián)。Java 編譯器會(huì)在 synchronized 修飾的方法或代碼塊前后自動(dòng)加上加鎖 lock() 和解鎖 unlock(),這樣做的好處就是加鎖 lock() 和解鎖 unlock() 一定是成對(duì)出現(xiàn)的,畢竟忘記解鎖 unlock() 可是個(gè)致命的 Bug(意味著其他線程只能死等下去了)。
  • C.實(shí)現(xiàn)同步是要很大的系統(tǒng)開(kāi)銷作為代價(jià)的,甚至可能造成死鎖,所以盡量避免無(wú)謂的同步控制。

以上內(nèi)容就是我對(duì)并法中的可見(jiàn)性的一點(diǎn)理解與總結(jié)了,下期我們接著敘述并發(fā)中的有序性。

參考文章:

1、極客時(shí)間的Java并發(fā)編程實(shí)戰(zhàn)

2、https://www.jianshu.com/p/89a8fa8ffe39

3、https://www.cnblogs.com/xiaonantianmen/p/9970368.html

4、https://www.lagou.com/lgeduarticle/78722.html

5、https://blog.csdn.net/evankaka/article/details/44153709

6、https://juejin.im/post/5d52abd1e51d4561e6237124

責(zé)任編輯:武曉燕 來(lái)源: Java極客技術(shù)
相關(guān)推薦

2021-07-06 14:47:30

Go 開(kāi)發(fā)技術(shù)

2022-07-10 20:49:57

javaVolatile線程

2016-11-11 00:39:59

Java可見(jiàn)性機(jī)制

2018-07-19 14:34:48

數(shù)據(jù)中心監(jiān)控網(wǎng)絡(luò)

2021-09-01 10:50:25

云計(jì)算云計(jì)算環(huán)境云應(yīng)用

2024-02-27 17:46:25

并發(fā)程序CPU

2011-11-29 13:09:02

2013-08-27 09:17:15

軟件定義網(wǎng)絡(luò)SDN網(wǎng)絡(luò)可見(jiàn)性

2020-08-25 09:51:40

Android 11開(kāi)發(fā)者軟件

2021-12-22 11:15:04

云計(jì)算混合云公有云

2023-04-06 15:47:23

2020-07-20 10:40:31

云計(jì)算云平臺(tái)IT

2018-05-26 16:01:37

2024-05-13 08:51:39

2016-07-04 08:19:13

混合IT網(wǎng)絡(luò)問(wèn)題SaaS

2024-02-18 13:34:42

云計(jì)算

2022-03-24 08:02:39

網(wǎng)絡(luò)安全端點(diǎn)

2016-07-29 17:08:30

修復(fù)網(wǎng)絡(luò)問(wèn)題

2024-10-14 14:49:59

2023-06-13 08:29:18

網(wǎng)絡(luò)可見(jiàn)性Cato
點(diǎn)贊
收藏

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