本篇內(nèi)容介紹了“單線程和多線程中的可見性的區(qū)別是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
專注于為中小企業(yè)提供成都網(wǎng)站建設(shè)、成都做網(wǎng)站服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)大埔免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上1000家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
當一個線程修改了某個共享變量時(非局部變量,所有線程都可以訪問得到),其他線程總是能立馬讀到最新值,這時我們就說這個變量是具有可見性的
如果是單線程,那么可見性是毋庸置疑的,肯定改了就能看到(直腸子,有啥說啥,大家都能看到)
但是如果是多線程,那么可見性就需要通過一些手段來維持了,比如加鎖或者volatile修飾符(花花腸子,各種套路讓人措手不及)
PS:實際上,沒有真正的直腸子,據(jù)科學研究表明,人的腸子長達8米左右(~身高的5倍)
單線程和多線程中的可見性對比
volatile修飾符
指令重排序
volatile和加鎖的區(qū)別
這里我們舉兩個例子來看下,來了解什么是可見性問題
下面是一個單線程的例子,其中有一個共享變量
public class SignleThreadVisibilityDemo { // 共享變量 private int number; public void setNumber(int number){ this.number = number; } public int getNumber(){ return this.number; } public static void main(String[] args) { SignleThreadVisibilityDemo demo = new SignleThreadVisibilityDemo(); System.out.println(demo.getNumber()); demo.setNumber(10); System.out.println(demo.getNumber()); } }
輸出如下:可以看到,第一次共享變量number為初始值0,但是調(diào)用setNumber(10)之后,再讀取就變成了10
0 10
改了就能看到,如果多線程也有這么簡單,那多好(來自菜鳥的內(nèi)心獨白)。
下面我們看一個多線程的例子,還是那個共享變量
package com.jalon.concurrent.chapter3; /** ** 可見性:多線程的可見性問題 *
* * @author: JavaLover * @time: 2021/4/27 */ public class MultiThreadVisibilityDemo { // 共享變量 private int number; public static void main(String[] args) throws InterruptedException { MultiThreadVisibilityDemo demo = new MultiThreadVisibilityDemo(); new Thread(()->{ // 這里我們做個假死循環(huán),只有沒給number賦值(初始化除外),就一直循環(huán) while (0==demo.number); System.out.println(demo.number); }).start(); Thread.sleep(1000); // 168不是身高,只是個比較吉利的數(shù)字 demo.setNumber(168); } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } }
輸出如下:
你沒看錯,就是輸出為空,而且程序還在一直運行(沒有試過,如果不關(guān)機,會不會有輸出number的那一天)
這時就出現(xiàn)了可見性問題,即主線程改了共享變量number,而子線程卻看不到
原因是什么呢?
我們用圖來說話吧,會輕松點
步驟如下:
子線程讀取number到自己的棧中,備份
主線程讀取number,修改,寫入,同步到內(nèi)存
子線程此時沒有意識到number的改變,還是讀自己棧中的備份ready(可能是各種性能優(yōu)化的原因)
那要怎么解決呢?
加鎖或者volatile修飾符,這里我們加volatile
修改后的代碼如下:
public class MultiThreadVisibilityDemo { // 共享變量,加了volatile修飾符,此時number不會備份到其他線程,只會存在共享的堆內(nèi)存中 private volatile int number; public static void main(String[] args) throws InterruptedException { MultiThreadVisibilityDemo demo = new MultiThreadVisibilityDemo(); new Thread(()->{ while (0==demo.number); System.out.println(demo.number); }).start(); Thread.sleep(1000); // 168不是身高,只是個比較吉利的數(shù)字 demo.setNumber(168); } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } }
輸出如下:
168
可以看到,跟我們預期的一樣,子線程可以看到主線程做的修改
下面就讓我們一起來探索volatile的小世界吧
volatile是一種比加鎖稍弱的同步機制,它和加鎖最大的區(qū)別就是,它不能保證原子性,但是它輕量啊
我們先把上面那個例子說完;
我們加了volatile修飾符后,子線程就可以看到主線程做的修改,那么volatile到底做了什么呢?
其實我們可以把volatile看做一個標志,如果虛擬機看到這個標志,就會認為被它修飾的變量是易變的,不穩(wěn)定的,隨時可能被某個線程修改;
此時虛擬機就不會對與這個變量相關(guān)的指令進行重排序(下面會講到),而且還會將這個變量的改變實時通知到各個線程(可見性)
用圖說話的話,就是下面這個樣子:
可以看到,線程中的number備份都不需要了,每次需要number的時候,都直接去堆內(nèi)存中讀取,這樣就保證了數(shù)據(jù)的可見性
指令重排序指的是,虛擬機有時候為了優(yōu)化性能,會把某些指令的執(zhí)行順序進行調(diào)整,前提是指令的依賴關(guān)系不能被破壞(比如int a = 10; int b = a;此時就不會重排序)
下面我們看下可能會重排序的代碼:
public class ReorderDemo { public static void main(String[] args) { int a = 1; int b = 2; int m = a + b; int c = 1; int d = 2; int n = c - d; } }
這里我們要了解一個底層知識,就是每一條語句的執(zhí)行,在底層系統(tǒng)都是分好幾步走的(比如第一步,第二步,第三步等等,這里我們就不涉及那些匯編知識了,大家感興趣可以參考看下《實戰(zhàn)Java高并發(fā)》1.5.4);
現(xiàn)在讓我們回到上面這個例子,依賴關(guān)系如下:
可以看到,他們?nèi)啥?,互不依賴,此時如果發(fā)生了重排序,那么就有可能排成下面這個樣子
(上圖只是從代碼層面進行的效果演示,實際上指令的重排序比這個細節(jié)很多,這里主要了解重排序的思想先)
由于m=a+b需要依賴a和b的值,所以當指令執(zhí)行到m=a+b的add環(huán)節(jié)時,如果b還沒準備好,那么m=a+b就需要等待b,后面的指令也會等待;
但是如果重排序,把m=a+b放到后面,那么就可以利用add等待的這個空檔期,去準備c和d;
這樣就減少了等待時間,提升了性能(感覺有點像上學時候?qū)W的C,習慣性地先定義變量一大堆,然后再編寫代碼)
區(qū)別如下
加鎖 | volatile | |
---|---|---|
原子性 | ? | ? |
可見性 | ? | ? |
有序性 | ? | ? |
上面所說的有序性指的就是禁止指令的重排序,從而使得多線程中不會出現(xiàn)亂序的問題;
我們可以看到,加鎖和volatile最大的區(qū)別就是原子性;
主要是因為volatile只是針對某個變量進行修飾,所以就有點像原子變量的復合操作(雖然原子變量本身是原子操作,但是多個原子變量放到一起,就無法保證了)
“單線程和多線程中的可見性的區(qū)別是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!