反骨之Java是如何解决并发中的可见性问题的
前言
前段時間筆者寫過一篇關(guān)于, 關(guān)于《反骨之Java是如何解決并發(fā)中的原子性問題》的博文。
其中,提出一個觀點:Java中使用互斥鎖和CAS解決了并發(fā)中的原子性問題。
那么,本篇博文則主要探討的是:
Java中如何利用Java內(nèi)存模型規(guī)范中的Volatile、synchronized、final關(guān)鍵字解決可見性問題。
(想自學(xué)習(xí)編程的小伙伴請搜索圈T社區(qū),更多行業(yè)相關(guān)資訊更有行業(yè)相關(guān)免費視頻教程。完全免費哦!)
正文
在開始重點之前,我們注意到上文提到的兩個關(guān)鍵詞,即內(nèi)存模型和可見性問題。
由于筆者在《反骨之硬件&軟件為Java并發(fā)編程中挖的坑(可見性&原子性&有序性)》一文中,對可見性僅是一筆帶過,所在筆者認為在本篇博文中,需要對可見性進行更進一步的解釋。
所以,在談解決可見性問題之前,我們需要聊聊可見性問題&Java內(nèi)存模型。
什么是可見性問題呢?
- 可見性,主要強調(diào)一個線程對某個共享變量進行更新之后,后續(xù)訪問該共享變量的線程可能無法立即讀取到更新后的最新結(jié)果,甚至永遠也無法得知其他線程對該共享變量進行過修改操作。那么,這種問題,我們可以稱其為:可見性問題。
當然,可見性問題是也是線程安全性的表現(xiàn)形式之一。
線程安全性的表現(xiàn)形式,即為:原子、有序、可見性。
什么是Java內(nèi)存模型(JMM)?
- 簡單來說,Java內(nèi)存模型是一組規(guī)范,這些規(guī)范告訴JVM如何解決原子性、有序性和可見性問題。
我們知道Java的對象一般是放在堆內(nèi)存中的,而堆內(nèi)存是線程共享的,所以Java內(nèi)存模型的影響范圍一般只涉及堆內(nèi)存。這里,JMM有一張比較形象的圖1-1:
我們可以看到,JMM處于線程和主內(nèi)存中間,充當中介人的角色。
當線程和主內(nèi)存只能通過JMM通信的時候,那么JMM就是唯一的主宰!
接下來是JMM的自我介紹:
大家好,我叫Java memory model(JMM)
為了更好的解決并發(fā)編程中的線程安全性問題,即保證并發(fā)中的原子性、有序性和可見性。
所以,我規(guī)定了以下規(guī)則。
規(guī)則一:每個線程都有獨立的工作內(nèi)存, 即工作內(nèi)存(本地線程)
規(guī)則二:所有的共享變量都必須在主內(nèi)存中, 且只能通過JMM進行控制訪問。
規(guī)則三:所有的共享變量都必須在主內(nèi)存中,每個線程都有自己的工作內(nèi)存(本地內(nèi)存),線程的所有操作都必須在工作內(nèi)存中進行,而不能直接對主內(nèi)存進行操作。
規(guī)則四:工作內(nèi)存之間禁止互相訪問。
掌握絕對權(quán)力之后,那么JMM就可以制定可見性規(guī)范:
比如:當線程A想跟線程B通信的時候
- 首先,線程A需要把自己本地內(nèi)存中更新后的共享變量副本,刷新到主內(nèi)存中。
- 隨后,線程B跳過讀取本地內(nèi)存,直接向主內(nèi)存中讀取共享變量的值,將主內(nèi)存中讀取的值放入自己的本地內(nèi)存中。
從這里可以看出,JMM是通過控制主內(nèi)存和每個線程的本地內(nèi)存之間的交互,來達到可見性目的的。
眾所周知,JMM是一組抽象的復(fù)雜的規(guī)范,那么如何把抽象的、復(fù)雜的規(guī)范變成現(xiàn)實可用的方法呢?
所以,Java到底是利用什么手段或措施,保證多線程環(huán)境下,共享變量是立即可見性的(鎖&volatile&final)。
- 手段一:synchronized或Lock——互斥鎖
在并發(fā)編程中,一旦使用互斥鎖,一般能解決所有并發(fā)問題!
所以,可見性可以使用互斥鎖進行保障。
- 手段二:volatile關(guān)鍵字
在這里筆者并不講volitale的底層實現(xiàn)原理,具體的底層實現(xiàn)細節(jié)筆者會單獨寫一篇博文來進行介紹。
其實,被volitale關(guān)鍵字修飾的共享變量,具有兩個語義(這里的語義,可以理解為潛規(guī)則)。
- 語義一:保證可見性。即,保證線程對共享變量的修改,對其他線程是立即可見的。
volatile關(guān)鍵字保證立即可見,其中有幾點需要注意:
其一,使用volatile關(guān)鍵字會強制將修改的值(共享變量)立即寫/讀入主內(nèi)存。
如何實現(xiàn)強制寫入主內(nèi)存?
在圖1-1中我們可以看到存在線程A和線程B。那么,當線程A對共享變量進行修改的時候,會導(dǎo)致線程B工作內(nèi)存中的共享變量副本失效。
如何實現(xiàn)強制讀取主內(nèi)存?
一旦線程工作內(nèi)存中的共享變量副本失效,那么就必須重新從主內(nèi)存中讀取最新的值。
當然,看到這里相信讀者們已經(jīng)看出來了,volitale的語義一是利用緩存一致性協(xié)議(MESI)來保證的。
- 語義二:保證有序性。
額…好像保證有序性在本篇博文,出現(xiàn)地有點不合時宜。
反正,讀者們只需要記住:
volitale關(guān)鍵字利用禁止指令重排序和禁止編譯優(yōu)化,保證有序性。
- 手段三:final關(guān)鍵字
有final修飾的變量(基本類型)具有不可變性,當且僅有一次賦值,一旦賦值即不可變。
不可變的變量或?qū)ο?#xff0c;我們可以稱其為線程安全變量/對象。
因為,final關(guān)鍵字修飾的變量是不可變的,在多線程環(huán)境中不管怎么操作,都是同一個值。
所以,final關(guān)鍵字是保證可見性的手段之一。
總結(jié)
-
Java內(nèi)存模型也稱為內(nèi)存一致性模型,是一些復(fù)雜規(guī)范的抽象集合,其中規(guī)定了工作內(nèi)存和主內(nèi)存的概念。
-
volitale關(guān)鍵字具有兩種語義:保證可見性&有序性。
-
final關(guān)鍵字意味著不可變(基本數(shù)據(jù)類型byte, int……),所以在多線程環(huán)境下是立即可見的。
-
synchronized、volitale、final三個關(guān)鍵字,可以看做是Java內(nèi)存模型對可見性問題提出的解決方案。
總結(jié)
以上是生活随笔為你收集整理的反骨之Java是如何解决并发中的可见性问题的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员不得不学的操作系统知识(三)
- 下一篇: java优化编程 第2版_Java优化编