欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java 并發(fā)編程之共享變量的實現(xiàn)方法

 更新時間:2019年09月20日 08:28:14   作者:sc_ik  
這篇文章主要介紹了java 并發(fā)編程之共享變量的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

可見性

如果一個線程對共享變量值的修改, 能夠及時的被其他線程看到, 叫做共享變量的可見性.

Java 虛擬機規(guī)范試圖定義一種 Java 內存模型 (JMM), 來屏蔽掉各種硬件和操作系統(tǒng)的內存訪問差異, 讓 Java 程序在各種平臺上都能達到一致的內存訪問效果.

簡單來說, 由于 CPU 執(zhí)行指令的速度是很快的, 但是內存訪問的速度就慢了很多, 相差的不是一個數(shù)量級, 所以搞處理器的那群大佬們又在 CPU 里加了好幾層高速緩存.

在 Java 內存模型里, 對上述的優(yōu)化又進行了一波抽象. JMM 規(guī)定所有變量都是存在主存中的, 類似于上面提到的普通內存, 每個線程又包含自己的工作內存, 方便理解就可以看成 CPU 上的寄存器或者高速緩存.

所以線程的操作都是以工作內存為主, 它們只能訪問自己的工作內存, 且工作前后都要把值在同步回主內存.

簡單點就是, 多線程中讀取或修改共享變量時, 首先會讀取這個變量到自己的工作內存中成為一個副本, 對這個副本進行改動后, 再更新回主內存中.

使用工作內存和主存, 雖然加快的速度, 但是也帶來了一些問題. 比如看下面一個例子:

i = i + 1;

假設 i 初值為 0, 當只有一個線程執(zhí)行它時, 結果肯定得到 1, 當兩個線程執(zhí)行時, 會得到結果 2 嗎? 這倒不一定了. 可能存在這種情況:

線程1: load i from 主存  // i = 0
    i + 1 // i = 1
線程2: load i from主存 // 因為線程1還沒將i的值寫回主內存,所以i還是0
    i + 1 //i = 1
線程1: save i to 主存
線程2: save i to 主存

如果兩個線程按照上面的執(zhí)行流程, 那么 i 最后的值居然是 1 了. 如果最后的寫回生效的慢, 你再讀取 i 的值, 都可能是 0, 這就是緩存不一致問題.

這種情況一般稱為 失效數(shù)據(jù), 因為線程1 還沒將 i 的值寫回主內存, 所以 i 還是 0, 在線程2 中讀到的就是 i 的失效值(舊值).

也可以理解成, 在操作完成之后將工作內存中的副本回寫到主內存, 并且在其它線程從主內存將變量同步回自己的工作內存之前, 共享變量的改變對其是不可見的.

有序性

有序性: 即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行. 舉個簡單的例子, 看下面這段代碼:

int i = 0;       
boolean flag = false;
i = 1;        //語句1 
flag = true;     //語句2

上面代碼定義了一個 int 型變量, 定義了一個 boolean 類型變量, 然后分別對兩個變量進行賦值操作.

從代碼順序上看, 語句1 是在語句2 前面的, 那么 JVM 在真正執(zhí)行這段代碼的時候會保證語句1 一定會在語句2 前面執(zhí)行嗎? 不一定, 為什么呢? 這里可能會發(fā)生指令重排序.

重排序

指令重排是指 JVM 在編譯 Java 代碼的時候, 或者 CPU 在執(zhí)行 JVM 字節(jié)碼的時候, 對現(xiàn)有的指令順序進行重新排序.

它不保證程序中各個語句的執(zhí)行先后順序同代碼中的順序一致, 但是它會保證程序最終執(zhí)行結果和代碼順序執(zhí)行的結果是一致的(指的是不改變單線程下的程序執(zhí)行結果).

雖然處理器會對指令進行重排序, 但是它會保證程序最終結果會和代碼順序執(zhí)行結果相同, 那么它靠什么保證的呢? 再看下面一個例子:

int a = 10;  //語句1
int r = 2;  //語句2
a = a + 3;  //語句3
r = a*a;   //語句4

這段代碼有 4 個語句, 那么可能的一個執(zhí)行順序是:

那么可不可能是這個執(zhí)行順序呢?

語句2 語句1 語句4 語句3.

不可能, 因為處理器在進行重排序時是會考慮指令之間的數(shù)據(jù)依賴性, 如果一個指令 Instruction 2 必須用到 Instruction 1 的結果, 那么處理器會保證 Instruction 1 會在 Instruction 2 之前執(zhí)行.

雖然重排序不會影響單個線程內程序執(zhí)行的結果, 但是多線程呢? 下面看一個例子:

//線程1:
context = loadContext();  //語句1
inited = true;       //語句2
 
//線程2:
while(!inited ){
 sleep()
}
doSomethingwithconfig(context);

上面代碼中, 由于語句1 和語句2 沒有數(shù)據(jù)依賴性, 因此可能會被重排序.

假如發(fā)生了重排序, 在線程1 執(zhí)行過程中先執(zhí)行語句2, 而此時線程2 會以為初始化工作已經(jīng)完成, 那么就會跳出 while 循環(huán), 去執(zhí)行 doSomethingwithconfig(context) 方法, 而此時 context 并沒有被初始化, 就會導致程序出錯.

從上面可以看出, 指令重排序不會影響單個線程的執(zhí)行, 但是會影響到線程并發(fā)執(zhí)行的正確性.

原子性

Java 中, 對基本數(shù)據(jù)類型的讀取和賦值操作是原子性操作, 所謂原子性操作就是指這些操作是不可中斷的, 要做一定做完, 要么就沒有執(zhí)行.

JMM 只實現(xiàn)了基本的原子性, 像 i++ 的操作, 必須借助于 synchronizedLock 來保證整塊代碼的原子性了. 線程在釋放鎖之前, 必然會把 i 的值刷回到主存的.

重點, 要想并發(fā)程序正確地執(zhí)行, 必須要保證原子性、可見性以及有序性. 只要有一個沒有被保證, 就有可能會導致程序運行不正確.

volatile 關鍵字

volatile 關鍵字的兩層語義

一旦一個共享變量 (類的成員變量、類的靜態(tài)成員變量) 被 volatile 修飾之后, 那么就具備了兩層語義:

1) 禁止進行指令重排序.

2) 讀寫一個變量時, 都是直接操作主內存.

在一個變量被 volatile 修飾后, JVM 會為我們做兩件事:

1.在每個 volatile 寫操作前插入 StoreStore 屏障, 在寫操作后插入 StoreLoad 屏障.

2.在每個 volatile 讀操作前插入 LoadLoad 屏障, 在讀操作后插入 LoadStore 屏障.

或許這樣說有些抽象, 我們看一看剛才線程A代碼的例子:

boolean contextReady = false;

//在線程A中執(zhí)行:
context = loadContext();
contextReady = true;

我們給 contextReady 增加 volatile 修飾符, 會帶來什么效果呢?

由于加入了 StoreStore 屏障, 屏障上方的普通寫入語句 context = loadContext() 和屏障下方的 volatile 寫入語句 contextReady = true 無法交換順序, 從而成功阻止了指令重排序.

也就是說, 當程序執(zhí)行到 volatile 變量的讀或寫操作時, 在其前面的操作的更改肯定全部已經(jīng)進行, 且結果已經(jīng)對后面的操作可見.

volatile特性之一:
保證變量在線程之間的可見性. 可見性的保證是基于 CPU 的內存屏障指令, 被 JSR-133 抽象為 happens-before 原則.

volatile特性之二:
阻止編譯時和運行時的指令重排. 編譯時 JVM 編譯器遵循內存屏障的約束, 運行時依靠 CPU 屏障指令來阻止重排.

volatile 除了保證可見性和有序性, 還解決了 long 類型和 double 類型數(shù)據(jù)的 8 字節(jié)賦值問題.
虛擬機規(guī)范中允許對 64 位數(shù)據(jù)類型, 分為 2 次 32 位的操作來處理, 當讀取一個非 volatile 類型的 long 變量時, 如果對該變量的讀操作和寫操作不在同一個線程中執(zhí)行, 那么很有可能會讀取到某個值得高 32 位和另一個值得低 32 位.

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • java連接HBase,連接不上報錯can not resolve問題及解決

    java連接HBase,連接不上報錯can not resolve問題及解決

    這篇文章主要介紹了java連接HBase,連接不上報錯can not resolve問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Spring中的事務管理及實現(xiàn)方式解析

    Spring中的事務管理及實現(xiàn)方式解析

    這篇文章主要介紹了Spring中的事務管理及實現(xiàn)方式解析,Spring事務管理基于底層數(shù)據(jù)庫本身的事務處理機制,數(shù)據(jù)庫事務的基礎,是掌握Spring事務管理的基礎,這篇總結下Spring事務,需要的朋友可以參考下
    2024-01-01
  • RabbitMQ之死信隊列深入解析

    RabbitMQ之死信隊列深入解析

    這篇文章主要介紹了RabbitMQ之死信隊列深入解析,?死信,顧名思義就是無法被消費的消息,字面意思可以這樣理解,一般來說,producer將消息投遞到 broker 或者直接到 queue 里了,consumer 從 queue 取消息進行消費,需要的朋友可以參考下
    2023-09-09
  • 解決Error:(5,55)java:程序包org.springframework.cloud.netflix.eureka.server不存在問題

    解決Error:(5,55)java:程序包org.springframework.cloud.netflix.eure

    這篇文章主要介紹了解決Error:(5,55)java:程序包org.springframework.cloud.netflix.eureka.server不存在問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • Spring處理@Async導致的循環(huán)依賴失敗問題的方案詳解

    Spring處理@Async導致的循環(huán)依賴失敗問題的方案詳解

    這篇文章主要為大家詳細介紹了SpringBoot中的@Async導致循環(huán)依賴失敗的原因及其解決方案,文中的示例代碼講解詳細,感興趣的可以學習一下
    2022-07-07
  • 一文解讀java.nio.ByteBuffer

    一文解讀java.nio.ByteBuffer

    這篇文章主要介紹了java.nio.ByteBuffer的用法解讀,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • Java在Excel中添加水印的實現(xiàn)(單一水印、平鋪水印)

    Java在Excel中添加水印的實現(xiàn)(單一水印、平鋪水印)

    這篇文章主要介紹了Java在Excel中添加水印的實現(xiàn)(單一水印、平鋪水印),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • SpringBoot如何優(yōu)雅地使用Swagger2

    SpringBoot如何優(yōu)雅地使用Swagger2

    這篇文章主要介紹了SpringBoot如何優(yōu)雅地使用Swagger2,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-07-07
  • 從零開始讓你的Spring?Boot項目跑在Linux服務器

    從零開始讓你的Spring?Boot項目跑在Linux服務器

    這篇文章主要給大家介紹了如何從零開始讓你的Spring?Boot項目跑在Linux服務器的相關資料,由于springboot是內嵌了tomcat,所以可以直接將項目打包上傳至服務器上,需要的朋友可以參考下
    2021-11-11
  • Java8新特性Stream短路終端操作實例解析

    Java8新特性Stream短路終端操作實例解析

    這篇文章主要介紹了Java8新特性Stream短路終端操作實例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-12-12

最新評論