真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

怎么理解Java并發(fā)可見性

本篇內(nèi)容介紹了“怎么理解Java并發(fā)可見性”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于做網(wǎng)站、成都網(wǎng)站建設(shè)、定遠(yuǎn)網(wǎng)絡(luò)推廣、重慶小程序開發(fā)、定遠(yuǎn)網(wǎng)絡(luò)營銷、定遠(yuǎn)企業(yè)策劃、定遠(yuǎn)品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎;創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供定遠(yuǎn)建站搭建服務(wù),24小時服務(wù)熱線:13518219792,官方網(wǎng)址:www.cdcxhl.com

01 可見性的闡述

可見性 的定義是:一個線程對共享變量的修改,另外一個線程能夠立刻看到。

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

怎么理解Java并發(fā)可見性

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

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

怎么理解Java并發(fā)可見性

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

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

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

通過一張圖片來表示就是(多核):

怎么理解Java并發(fā)可見性

下文我們的闡述,若無特殊說明,都是基于多核的。

02 原因分析

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

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

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

怎么理解Java并發(fā)可見性

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

例如下面這段代碼

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

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

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

怎么理解Java并發(fā)可見性

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

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

因為主線程對共享變量的修改沒有及時更新,子線程中不能立即得到最新值,導(dǎo)致程序不能按照預(yù)期結(jié)果執(zhí)行。

例如下面這段代碼:

package com.itquan.service.share.resources.controller;  import java.time.LocalDateTime;  /**  * @author :mmzsblog  * @description:共享變量在線程間的可見性測試  */ public class VisibilityDemo {      // 狀態(tài)標(biāo)識flag     private static boolean flag = true;      public static void main(String[] args) throws InterruptedException {         System.out.println(LocalDateTime.now() + "主線程啟動計數(shù)子線程");         new CountThread().start();          Thread.sleep(1000);         // 設(shè)置flag為false,使上面啟動的子線程跳出while循環(huán),結(jié)束運行         VisibilityDemo.flag = false;         System.out.println(LocalDateTime.now() + "主線程將狀態(tài)標(biāo)識flag被置為false了");     }      static class CountThread extends Thread {         @Override         public void run() {             System.out.println(LocalDateTime.now() + "計數(shù)子線程start計數(shù)");             int i = 0;             while (VisibilityDemo.flag) {                 i++;             }             System.out.println(LocalDateTime.now() + "計數(shù)子線程end計數(shù),運行結(jié)束:i的值是" + i);         }     }  }

運行結(jié)果是:

怎么理解Java并發(fā)可見性

從控制臺的打印結(jié)果可以看出,因為主線程對flag的修改,對計數(shù)子線程沒有立即可見,所以導(dǎo)致了計數(shù)子線程久久不能跳出while循環(huán),結(jié)束子線程。

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

03 如何解決線程間不可見性

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

3.1、volatile:只保證可見性

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

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

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

private static volatile boolean flag = true;

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

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

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

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

private static AtomicBoolean flag = new AtomicBoolean(true);

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

// 設(shè)置flag的值     VisibilityDemo.flag.set(false);          // 獲取flag的值     VisibilityDemo.flag.get()

3.3、Lock: 保證可見性和原子性

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

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

synchronized (this) {         i++;     }

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

怎么理解Java并發(fā)可見性

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

04  可見性-volatile

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

  • 第一、保證了不同線程對這個變量進(jìn)行讀取時的可見性。即一個線程修改了某個變量的值,  這個新值對其他線程來說是立即可見的。(volatile解決了線程間共享變量的可見性問題)。

  • 第二、禁止進(jìn)行指令重排序, 阻止編譯器對代碼的優(yōu)化。

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

  • 1:使用 volatile 關(guān)鍵字會強制將在某個線程中修改的共享變量的值立即寫入主內(nèi)存。

  • 2:使用 volatile 關(guān)鍵字的話, 當(dāng)線程 2 進(jìn)行修改時, 會導(dǎo)致線程 1 的工作內(nèi)存中變量的緩存行無效(反映到硬件層的話, 就是 CPU 的  L1或者 L2 緩存中對應(yīng)的緩存行無效);

附一張CPU緩存模型圖:

怎么理解Java并發(fā)可見性

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

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

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

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

怎么理解Java并發(fā)可見性

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

05 可見性synchronized

怎么理解Java并發(fā)可見性

5.1、作用域

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

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

如果一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法。

這時,不同的對象實例的synchronized方法是不相干擾的。也就是說,其它線程照樣可以同時訪問相同類的另一個對象實例中的synchronized方法。

因為當(dāng)修飾非靜態(tài)方法的時候,鎖定的是當(dāng)前實例對象。

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

因為當(dāng)修飾靜態(tài)方法的時候,鎖定的是當(dāng)前類的 Class 對象。

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

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

用法是:

synchronized(this){     /*區(qū)塊*/ }

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

5.3、不能繼承

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

synchronized f(){     // 具體操作 }

在繼承類中并不自動是

synchronized f(){     // 具體操作 }

而是變成了

f(){     // 具體操作 }

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

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

  • 修飾實例方法:作用于當(dāng)前實例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實例的鎖

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

  • 修飾代碼塊:指定加鎖對象,對給定對象加鎖,進(jìn)入同步代碼塊前要獲得給定對象的鎖

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

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

  • 線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存

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

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

但我們在使用synchronized保證可見性的時候也要注意以下幾點:

  • A.無論synchronized關(guān)鍵字加在方法上還是對象上,它取得的鎖都是對象;而不是把一段代碼或函數(shù)當(dāng)作鎖――而且同步方法很可能還會被其他線程的對象訪問。

  • B.每個對象只有一個鎖(lock)與之相關(guān)聯(lián)。Java 編譯器會在 synchronized 修飾的方法或代碼塊前后自動加上加鎖 lock() 和解鎖  unlock(),這樣做的好處就是加鎖 lock() 和解鎖 unlock() 一定是成對出現(xiàn)的,畢竟忘記解鎖 unlock() 可是個致命的  Bug(意味著其他線程只能死等下去了)。

  • C.實現(xiàn)同步是要很大的系統(tǒng)開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。

“怎么理解Java并發(fā)可見性”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!


分享文章:怎么理解Java并發(fā)可見性
當(dāng)前鏈接:http://weahome.cn/article/pdejjp.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部