日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

java并发-内存模型与volatile

發(fā)布時(shí)間:2025/3/15 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java并发-内存模型与volatile 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

JMM的關(guān)鍵技術(shù)點(diǎn)都是圍繞著多線程的原子性、可見性和有序性來建立的。因此,我們首先必須了解這些概念

1,原子性

原子性是指一個(gè)操作是不可中斷的。即使是在多個(gè)線程一起執(zhí)行的時(shí)候,一個(gè)操作一旦開始,就不會(huì)被其他線程干擾,比如,對(duì)于一個(gè)靜態(tài)全局變量int i,兩個(gè)線程同時(shí)對(duì)它賦值,線程A給他賦值1,線程B給它賦值為-1。那么不管這2個(gè)線程以何種方式、何種步調(diào)工作,i的值要么是1,要么是-1。線程A和線程B之間是沒有干擾的。這就是原子性的一個(gè)特點(diǎn),不可被中斷

2,可見性(Visibility)

可見性是指當(dāng)一個(gè)線程修改了某一個(gè)共享變量的值,其他線程是否能夠立即知道這個(gè)修改顯然,對(duì)于串行程序來說,可見性問題是不存在的。因?yàn)槟阍谌魏我粋€(gè)操作步驟中修改了某個(gè)變量,那么在后續(xù)的步驟中,讀取這個(gè)變量的值,一定是修改后的新值。

3 有序性(Ordering)

有序性問題可能是三個(gè)問題中最難理解的了。對(duì)于一個(gè)線程的執(zhí)行代碼而言,我們總是習(xí)慣地認(rèn)為代碼的執(zhí)行是從先往后,依次執(zhí)行的。這么理解也不能說完全錯(cuò)誤,因?yàn)榫鸵粋€(gè)線程內(nèi)而言,確實(shí)會(huì)表現(xiàn)成這樣。但是,在并發(fā)時(shí),程序的執(zhí)行可能就會(huì)出現(xiàn)亂序。給人直觀的感覺就是:寫在前面的代碼,會(huì)在后面執(zhí)行。聽起來有些不可思議,是嗎?有序性問題的原因是因?yàn)槌绦蛟趫?zhí)行時(shí),可能會(huì)進(jìn)行指令重排,重排后的指令與原指令的順序未必一致

                                                                                                                   以上概念均摘自《實(shí)戰(zhàn)java高并發(fā)程序設(shè)計(jì)》

Java內(nèi)存模型規(guī)定所有的變量都是存在主存當(dāng)中,每個(gè)線程都有自己的工作內(nèi)存。線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接對(duì)主存進(jìn)行操作。并且每個(gè)線程不能訪問其他線程的工作內(nèi)存。

主內(nèi)存和工作內(nèi)存

Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣底層細(xì)節(jié)。此處的變量與Java編程時(shí)所說的變量不一樣,指包括了實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但是不包括局部變量與方法參數(shù),后者是線程私有的,不會(huì)被共享。

Java內(nèi)存模型中規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存(可以與前面講的處理器的高速緩存類比),線程的工作內(nèi)存中保存了該線程使用到的變量到主內(nèi)存副本拷貝,線程對(duì)變量的所有操作(讀取、賦值)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。不同線程之間無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要在主內(nèi)存來完成,線程、主內(nèi)存和工作內(nèi)存的交互關(guān)系如下圖所示,和上圖很類似。

注意:這里的主內(nèi)存、工作內(nèi)存與Java內(nèi)存區(qū)域的Java堆、棧、方法區(qū)不是同一層次內(nèi)存劃分,這兩者基本上沒有關(guān)系。

內(nèi)存交互操作

由上面的交互關(guān)系可知,關(guān)于主內(nèi)存與工作內(nèi)存之間的具體交互協(xié)議,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步到主內(nèi)存之間的實(shí)現(xiàn)細(xì)節(jié),Java內(nèi)存模型定義了以下八種操作來完成:

  • lock(鎖定):作用于主內(nèi)存的變量,把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占狀態(tài)。
  • unlock(解鎖):作用于主內(nèi)存變量,把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
  • read(讀取):作用于主內(nèi)存變量,把一個(gè)變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用
  • load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
  • use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。
  • assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。
  • store(存儲(chǔ)):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write的操作。
  • write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存的變量中。

如果要把一個(gè)變量從主內(nèi)存中復(fù)制到工作內(nèi)存,就需要按順尋地執(zhí)行read和load操作,如果把變量從工作內(nèi)存中同步回主內(nèi)存中,就要按順序地執(zhí)行store和write操作。Java內(nèi)存模型只要求上述兩個(gè)操作必須按順序執(zhí)行,而沒有保證必須是連續(xù)執(zhí)行。也就是read和load之間,store和write之間是可以插入其他指令的,如對(duì)主內(nèi)存中的變量a、b進(jìn)行訪問時(shí),可能的順序是read a,read b,load b, load a。Java內(nèi)存模型還規(guī)定了在執(zhí)行上述八種基本操作時(shí),必須滿足如下規(guī)則:

  • 不允許read和load、store和write操作之一單獨(dú)出現(xiàn)
  • 不允許一個(gè)線程丟棄它的最近assign的操作,即變量在工作內(nèi)存中改變了之后必須同步到主內(nèi)存中。
  • 不允許一個(gè)線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步回主內(nèi)存中。
  • 一個(gè)新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)的變量。即就是對(duì)一個(gè)變量實(shí)施use和store操作之前,必須先執(zhí)行過了assign和load操作。
  • 一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,lock和unlock必須成對(duì)出現(xiàn)
  • 如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值
  • 如果一個(gè)變量事先沒有被lock操作鎖定,則不允許對(duì)它執(zhí)行unlock操作;也不允許去unlock一個(gè)被其他線程鎖定的變量。
  • 對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)。

關(guān)于volatile

Java 使用了一些特殊的操作或者關(guān)鍵字來申明、告訴虛擬機(jī),在這個(gè)地方,要尤其注意,不能隨意變動(dòng)優(yōu)化目標(biāo)指令。關(guān)鍵字volatile 就是其中之一。當(dāng)你用volatile 去申明一個(gè)變量時(shí),就等于告訴了虛擬機(jī),這個(gè)變量極有可能會(huì)被某些程序或者線程修改。為了確保這個(gè)變量被修改后,應(yīng)用程序范圍內(nèi)的所有線程都能夠“看到”這個(gè)改動(dòng),虛擬機(jī)就必須采用一些特殊的手段,保證這個(gè)變量的可見性等特點(diǎn)。

先來看一個(gè)使用場(chǎng)景,如下面一段代碼在32位java虛擬機(jī)上的運(yùn)行

public class Test {public static long t = 0;public static class ChangeT implements Runnable {private long to;public ChangeT(long to) {this.to = to;}@Overridepublic void run() {while (true) {Test.t = to;Thread.yield();}}}public static class ReadT implements Runnable {@Overridepublic void run() {while (true) {long temp = Test.t;if (temp != 111L && temp != -999L && temp != 333L && temp != -444L) {System.out.println(temp);}Thread.yield();}}}public static void main(String args[]) {new Thread(new ChangeT(111L)).start();new Thread(new ChangeT(-999L)).start();new Thread(new ChangeT(333L)).start();new Thread(new ChangeT(-444L)).start();new Thread(new ReadT()).start();} }

可以看出由于long是64位在32位的虛擬機(jī)上通過多線程賦值,導(dǎo)致數(shù)據(jù)的讀和寫都不是原子性的,因而造成,打印出的數(shù)字部分是亂的

-4294966963 4294966297 -4294966963 -4294966963 4294966297 -4294967185 -4294966963 4294966297 ...

但當(dāng)使用 volatile修飾后就沒有問題了

public volatile static long t = 0;

?

同時(shí)也需要注意volatile是無法完全保證一些復(fù)合操作的原子性,它并不能夠代替鎖。

package com.ishop.controller;public class Test {public volatile static int i = 0;public static void main(String[] args) throws InterruptedException {AddThread t1 = new AddThread();AddThread t2 = new AddThread();AddThread t3 = new AddThread();AddThread t4 = new AddThread();t1.start();t2.start();t3.start();t4.start();System.out.println("i="+i);} } class AddThread extends Thread {@Overridepublic void run() {for (int a = 0; a < 100000; a++){Test.i++;}} }

如:這一段代碼,輸出i總是一個(gè)小于100000的值。

對(duì)于volatile的特點(diǎn)以及使用場(chǎng)景總結(jié)如下:

volatile變量具有2種特性:

  • 保證變量的可見性。對(duì)一個(gè)volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫入,這個(gè)新值對(duì)于其他線程來說是立即可見的。
  • 屏蔽指令重排序:指令重排序是編譯器和處理器為了高效對(duì)程序進(jìn)行優(yōu)化的手段,下文有詳細(xì)的分析。

volatile語義并不能保證變量的原子性。對(duì)任意單個(gè)volatile變量的讀/寫具有原子性,但類似于i++、i–這種復(fù)合操作不具有原子性,因?yàn)樽栽鲞\(yùn)算包括讀取i的值、i值增加1、重新賦值3步操作,并不具備原子性。

由于volatile只能保證變量的可見性和屏蔽指令重排序,只有滿足下面2條規(guī)則時(shí),才能使用volatile來保證并發(fā)安全,否則就需要加鎖(使用synchronized、lock或者java.util.concurrent中的Atomic原子類)來保證并發(fā)中的原子性。

  • 運(yùn)算結(jié)果不存在數(shù)據(jù)依賴(重排序的數(shù)據(jù)依賴性),或者只有單一的線程修改變量的值(重排序的as-if-serial語義)
  • 變量不需要與其他的狀態(tài)變量共同參與不變約束

因?yàn)樾枰诒镜卮a中插入許多內(nèi)存屏蔽指令在屏蔽特定條件下的重排序,volatile變量的寫操作與讀操作相比慢一些,但是其性能開銷比鎖低很多。

?

?

?

?

?

參考:《實(shí)戰(zhàn)java高并發(fā)程序設(shè)計(jì)》

文章部分內(nèi)容引自:http://blog.csdn.net/u011080472/article/details/51337422

轉(zhuǎn)載于:https://www.cnblogs.com/china2k/p/8005770.html

總結(jié)

以上是生活随笔為你收集整理的java并发-内存模型与volatile的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。