最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Java并發(fā)那些事

2021-05-10 10:08 作者:光耀三十洲  | 我要投稿

主要講解Java的并發(fā)編程的基礎(chǔ)知識(shí),包括原子性、可見(jiàn)性、有序性,以及內(nèi)存模型JMM。

Java系列說(shuō)明

從這篇文章開(kāi)始,我就要正式開(kāi)始學(xué)習(xí)Java了,之所以說(shuō)是從現(xiàn)在開(kāi)始,是因?yàn)榍皟蓚€(gè)月一直在糾結(jié)是否轉(zhuǎn)技術(shù)棧(細(xì)心的同學(xué)可以發(fā)現(xiàn),我之前寫(xiě)的文章,其實(shí)和Java并沒(méi)有什么關(guān)系),現(xiàn)在已經(jīng)想清楚了,既然確定要轉(zhuǎn)Java技術(shù)棧,那就踏踏實(shí)實(shí)從頭開(kāi)始學(xué)吧。

學(xué)習(xí)更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1F54y177YZ

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1154y157e5

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1nB4y1w7BT

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1tZ4y1F7eK

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV135411w7Jg

目前的我,可以說(shuō)是Java小白,剛轉(zhuǎn)團(tuán)隊(duì)不久,也就接觸了2個(gè)月的Java,代碼沒(méi)寫(xiě)幾行,既然發(fā)現(xiàn)自己Java很菜,那就要列個(gè)學(xué)習(xí)計(jì)劃,將這塊知識(shí)好好補(bǔ)補(bǔ)。目前給自己定了一年的學(xué)習(xí)計(jì)劃,希望能通過(guò)一年的學(xué)習(xí),將Java的技能從初階直接晉級(jí)到高階水平,可能有同學(xué)會(huì)問(wèn)“我學(xué)習(xí)Java都幾年的,都還是中級(jí)水平,你花一年就可以晉級(jí)到高階?”,我只想說(shuō),我想試試,畢竟工作這么長(zhǎng)時(shí)間,也掌握了一定的學(xué)習(xí)方法,相信跟著自己的學(xué)習(xí)節(jié)奏走,應(yīng)該不會(huì)離目標(biāo)太遠(yuǎn),今天立個(gè)Flag,希望一年后不會(huì)啪啪打臉【捂臉】~~

Java系列的內(nèi)容主要包括并發(fā)編程、Spring、SpringBoost、SpringCloud、Tomcat、MyBatis、Dubbo和虛擬機(jī),然后一些經(jīng)典書(shū)籍的讀書(shū)筆記等,當(dāng)這些都掌握到一定深度后,我想我的Java技能應(yīng)該也就差不多了。

學(xué)習(xí)更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1F54y177YZ

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1154y157e5

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1nB4y1w7BT

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1tZ4y1F7eK

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV135411w7Jg

最后想說(shuō)的是,Java很多系列文章,很大一部分是內(nèi)容整理,之所以要通過(guò)文章的形式再寫(xiě)一遍,是因?yàn)榭催^(guò)的內(nèi)容,如果自己不整理一遍,或者不讓程序跑跑,很容易遺忘,所以寫(xiě)文章其實(shí)不是目的,主要是重新整理和回顧學(xué)習(xí)內(nèi)容的過(guò)程,一方面印象深刻,另一方面,也便于自己后續(xù)查閱。

今天廢話有點(diǎn)多,我們就從并發(fā)編程開(kāi)始吧!

并發(fā)編程基本概念

原子性

一個(gè)操作或者多個(gè)操作,要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行。

原子性是拒絕多線程操作的,不論是多核還是單核,具有原子性的量,同一時(shí)刻只能有一個(gè)線程來(lái)對(duì)它進(jìn)行操作。簡(jiǎn)而言之,在整個(gè)操作過(guò)程中不會(huì)被線程調(diào)度器中斷的操作,都可認(rèn)為是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。Java中的原子性操作包括:

  • 基本類型的讀取和賦值操作,且賦值必須是值賦給變量,變量之間的相互賦值不是原子性操作;

  • 所有引用reference的賦值操作;

  • java.concurrent.Atomic.* 包中所有類的一切操作。

可見(jiàn)性

指當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值。

在多線程環(huán)境下,一個(gè)線程對(duì)共享變量的操作對(duì)其他線程是不可見(jiàn)的。Java提供了volatile來(lái)保證可見(jiàn)性,當(dāng)一個(gè)變量被volatile修飾后,表示著線程本地內(nèi)存無(wú)效,當(dāng)一個(gè)線程修改共享變量后他會(huì)立即被更新到主內(nèi)存中,其他線程讀取共享變量時(shí),會(huì)直接從主內(nèi)存中讀取。當(dāng)然,synchronize和Lock都可以保證可見(jiàn)性。synchronized和Lock能保證同一時(shí)刻只有一個(gè)線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會(huì)將對(duì)變量的修改刷新到主存當(dāng)中。因此可以保證可見(jiàn)性。

學(xué)習(xí)更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1F54y177YZ

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1154y157e5

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1nB4y1w7BT

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1tZ4y1F7eK

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV135411w7Jg

有序性

即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

Java內(nèi)存模型中的有序性可以總結(jié)為:如果在本線程內(nèi)觀察,所有操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有操作都是無(wú)序的。前半句是指“線程內(nèi)表現(xiàn)為串行語(yǔ)義”,后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存主主內(nèi)存同步延遲”現(xiàn)象。

在Java內(nèi)存模型中,為了效率是允許編譯器和處理器對(duì)指令進(jìn)行重排序,當(dāng)然重排序不會(huì)影響單線程的運(yùn)行結(jié)果,但是對(duì)多線程會(huì)有影響。Java提供volatile來(lái)保證一定的有序性。最著名的例子就是單例模式里面的DCL(雙重檢查鎖)。另外,可以通過(guò)synchronized和Lock來(lái)保證有序性,synchronized和Lock保證每個(gè)時(shí)刻是有一個(gè)線程執(zhí)行同步代碼,相當(dāng)于是讓線程順序執(zhí)行同步代碼,自然就保證了有序性。

學(xué)習(xí)更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1F54y177YZ

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1154y157e5

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1nB4y1w7BT

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1tZ4y1F7eK

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV135411w7Jg

為了讓大家更好理解可見(jiàn)性和有序性,這個(gè)就不得不了解“內(nèi)存模型”、“重排序”和“內(nèi)存屏障”,因?yàn)檫@三個(gè)概念和他們關(guān)系非常密切。

內(nèi)存模型

JMM決定一個(gè)線程對(duì)共享變量的寫(xiě)入何時(shí)對(duì)另一個(gè)線程可見(jiàn),JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:共享變量存儲(chǔ)在主內(nèi)存(Main Memory)中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(Local Memory),本地內(nèi)存保存了被該線程使用到的主內(nèi)存的副本拷貝,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存中的變量。

對(duì)于普通的共享變量來(lái)講,線程A將其修改為某個(gè)值發(fā)生在線程A的本地內(nèi)存中,此時(shí)還未同步到主內(nèi)存中去;而線程B已經(jīng)緩存了該變量的舊值,所以就導(dǎo)致了共享變量值的不一致。解決這種共享變量在多線程模型中的不可見(jiàn)性問(wèn)題,可以使用volatile、synchronized、final等,此時(shí)A、B的通信過(guò)程如下:

學(xué)習(xí)更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1F54y177YZ

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1154y157e5

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1nB4y1w7BT

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1tZ4y1F7eK

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV135411w7Jg

  • 首先,線程A把本地內(nèi)存A中更新過(guò)的共享變量刷新到主內(nèi)存中去;

  • 然后,線程B到主內(nèi)存中去讀取線程A之前已更新過(guò)的共享變量。

JMM通過(guò)控制主內(nèi)存與每個(gè)線程的本地內(nèi)存之間的交互,來(lái)為java程序員提供內(nèi)存可見(jiàn)性保證,需要注意的是,JMM是個(gè)抽象的內(nèi)存模型,所以所謂的本地內(nèi)存,主內(nèi)存都是抽象概念,并不一定就真實(shí)的對(duì)應(yīng)cpu緩存和物理內(nèi)存。

總結(jié)一句話,內(nèi)存模型JMM控制多線程對(duì)共享變量的可見(jiàn)性!?。?/p>

重排序

重排序是指編譯器和處理器為了優(yōu)化程序性能而對(duì)指令序列進(jìn)行排序的一種手段。

重排序需要遵守一定規(guī)則:

  • 重排序操作不會(huì)對(duì)存在數(shù)據(jù)依賴關(guān)系的操作進(jìn)行重排序。比如:a=1;b=a; 這個(gè)指令序列,由于第二個(gè)操作依賴于第一個(gè)操作,所以在編譯時(shí)和處理器運(yùn)行時(shí)這兩個(gè)操作不會(huì)被重排序。

  • 重排序是為了優(yōu)化性能,但是不管怎么重排序,單線程下程序的執(zhí)行結(jié)果不能被改變。 比如:a=1;b=2;c=a+b這三個(gè)操作,第一步(a=1)和第二步(b=2)由于不存在數(shù)據(jù)依賴關(guān)系, 所以可能會(huì)發(fā)生重排序,但是c=a+b這個(gè)操作是不會(huì)被重排序的,因?yàn)樾枰WC最終的結(jié)果一定是c=a+b=3。

重排序在單線程下一定能保證結(jié)果的正確性,但是在多線程環(huán)境下,可能發(fā)生重排序,影響結(jié)果,請(qǐng)看下面的示例代碼:

學(xué)習(xí)更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1F54y177YZ

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1154y157e5

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1nB4y1w7BT

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1tZ4y1F7eK

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV135411w7Jg

flag變量是個(gè)標(biāo)記,用來(lái)標(biāo)識(shí)變量a是否已被寫(xiě)入。這里假設(shè)有兩個(gè)線程A和B,A首先執(zhí)行writer()方法,隨后B線程接著執(zhí)行reader()方法。線程B在執(zhí)行操作4時(shí),輸出是多少呢?

答案是:可能是0,也可能是1。

由于操作1和操作2沒(méi)有數(shù)據(jù)依賴關(guān)系,編譯器和處理器可以對(duì)這兩個(gè)操作重排序;同樣,操作3和操作4沒(méi)有數(shù)據(jù)依賴關(guān)系,編譯器和處理器也可以對(duì)這兩個(gè)操作重排序。讓我們先來(lái)看看,當(dāng)操作1和操作2重排序時(shí),可能會(huì)產(chǎn)生什么效果?請(qǐng)看下面的程序執(zhí)行時(shí)序圖:

如上圖所示,操作1和操作2做了重排序。程序執(zhí)行時(shí),線程A首先寫(xiě)標(biāo)記變量flag,隨后線程B讀這個(gè)變量。由于條件判斷為真,線程B將讀取變量a。此時(shí),變量a還根本沒(méi)有被線程A寫(xiě)入,在這里多線程程序的語(yǔ)義被重排序破壞了!最后輸出i的結(jié)果是0。

學(xué)習(xí)更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1F54y177YZ

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1154y157e5

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1nB4y1w7BT

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1tZ4y1F7eK

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV135411w7Jg

溫馨提示:這里其實(shí)理解起來(lái)有點(diǎn)繞,比如線程A先執(zhí)行了writer(),然后線程B執(zhí)行reader(),對(duì)于線程A,怎么會(huì)有這個(gè)重排序呢?其實(shí)這個(gè)重排序,是對(duì)線程B而言的,不是線程A哈!

有了線程B這第一視角,我們?cè)倮斫庖幌?,雖然線程A將writer()執(zhí)行了,執(zhí)行順序是a=1,flag=true,但是對(duì)于線程B來(lái)說(shuō),因?yàn)橹嘏判?,線程B是根據(jù)重排序后的結(jié)果去執(zhí)行的,所以才會(huì)出現(xiàn)上述異常情況,這么給大家解釋,是不是就清晰很多呢?

下面再讓我們看看,當(dāng)操作3和操作4重排序時(shí)會(huì)產(chǎn)生什么效果(借助這個(gè)重排序,可以順便說(shuō)明控制依賴性)。下面是操作3和操作4重排序后,程序的執(zhí)行時(shí)序圖:

在程序中,操作3和操作4存在控制依賴關(guān)系。當(dāng)代碼中存在控制依賴性時(shí),會(huì)影響指令序列執(zhí)行的并行度。為此,編譯器和處理器會(huì)采用猜測(cè)(Speculation)執(zhí)行來(lái)克服控制相關(guān)性對(duì)并行度的影響。以處理器的猜測(cè)執(zhí)行為例,執(zhí)行線程B的處理器可以提前讀取并計(jì)算a*a,此時(shí)結(jié)果為0,然后把計(jì)算結(jié)果臨時(shí)保存到一個(gè)名為重排序緩沖(reorder buffer ROB)的硬件緩存中。當(dāng)接下來(lái)操作3的條件判斷為真時(shí),就把該計(jì)算結(jié)果寫(xiě)入變量i中。

學(xué)習(xí)更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1F54y177YZ

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1154y157e5

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1nB4y1w7BT

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1tZ4y1F7eK

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV135411w7Jg

從圖中我們可以看出,猜測(cè)執(zhí)行實(shí)質(zhì)上對(duì)操作3和4做了重排序。重排序在這里破壞了多線程程序的語(yǔ)義!因?yàn)閠emp的值為0,所以最后輸出i的結(jié)果是0。

那如何避免重排序?qū)Χ嗑€程的影響呢,答案是“內(nèi)存屏障”!

內(nèi)存屏障

為了保證內(nèi)存可見(jiàn)性,可以通過(guò)volatile、final等修飾變量,java編譯器在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障指令來(lái)禁止特定類型的處理器重排序。內(nèi)存屏障主要有3個(gè)功能:

  • 它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí),在它前面的操作已經(jīng)全部完成;

  • 它會(huì)強(qiáng)制將對(duì)緩存的修改操作立即寫(xiě)入主存;

  • 如果是寫(xiě)操作,它會(huì)導(dǎo)致其他CPU中對(duì)應(yīng)的緩存行無(wú)效。

假如我對(duì)上述示例的falg變量通過(guò)volatile修飾:

這個(gè)時(shí)候,volatile禁止指令重排序也有一些規(guī)則,因?yàn)槠?,改?guī)則將會(huì)在下一章講解,根據(jù)happens before規(guī)則,這個(gè)過(guò)程建立的happens before 關(guān)系可以分為兩類:

  1. 根據(jù)程序次序規(guī)則,1 happens before 2; 3 happens before 4。

  2. 根據(jù)volatile規(guī)則,2 happens before 3。

  3. 根據(jù)happens before 的傳遞性規(guī)則,1 happens before 4。

happens before規(guī)則,其實(shí)就是重排序規(guī)則建立的代碼前后依賴關(guān)系。

溫馨提示:這里大家可能會(huì)有疑問(wèn),1、3的規(guī)則我理解,但是對(duì)于2,為什么“2 happens before 3”,還記得前面講的“內(nèi)存模型”么?因?yàn)槟銓?duì)變量flag指定了volatile,所以當(dāng)線程A執(zhí)行完后,變量flag=true會(huì)直接刷到內(nèi)存中,然后B馬上可見(jiàn),所以說(shuō)2一定是在3前面,不可能因?yàn)橹嘏判颍瑢?dǎo)致3在2前面執(zhí)行。(然后還要提示一下,這里執(zhí)行時(shí)有個(gè)前提條件,就是線程A執(zhí)行完,才能執(zhí)行線程B里面的邏輯,因?yàn)榫€程A不執(zhí)行完,flag一直是false,線程B根本就進(jìn)不到主流程,所以你也可以直接理解為線程A執(zhí)行完后,再執(zhí)行線程B,才有這么個(gè)先后關(guān)系。)

上述happens before關(guān)系的圖形化表現(xiàn)形式如下:

在上圖中,每一個(gè)箭頭鏈接的兩個(gè)節(jié)點(diǎn),代表了一個(gè)happens before 關(guān)系。黑色箭頭表示程序順序規(guī)則;橙色箭頭表示volatile規(guī)則;藍(lán)色箭頭表示組合這些規(guī)則后提供的happens before保證。

這里A線程寫(xiě)一個(gè)volatile變量后,B線程讀同一個(gè)volatile變量。A線程在寫(xiě)volatile變量之前所有可見(jiàn)的共享變量,在B線程讀同一個(gè)volatile變量后,將立即變得對(duì)B線程可見(jiàn)。

總結(jié)

今天講解了Java并發(fā)編程的3個(gè)特性,然后基于里面的兩個(gè)特性“可見(jiàn)性”和“有序性”引出幾個(gè)重要的概念,分別為“內(nèi)存模型JMM”、“重排序”和“內(nèi)存屏障”,這個(gè)對(duì)后續(xù)理解volatile、synchronized、final,以及避免使用的各種坑,真的是非常非常重要?。。∷赃@塊知識(shí)要必須!一定!!要?。?!掌握。

學(xué)習(xí)更多,請(qǐng)點(diǎn)擊:https://www.bilibili.com/video/BV1F54y177YZ

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1154y157e5

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1nB4y1w7BT

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1tZ4y1F7eK

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV135411w7Jg


作者:樓仔
鏈接:https://juejin.cn/post/6960211122485985317
來(lái)源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。


Java并發(fā)那些事的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
兴隆县| 会昌县| 遂昌县| 泰宁县| 石屏县| 东源县| 宜兰县| 呼图壁县| 彝良县| 平塘县| 宁明县| 安化县| 齐河县| 西乌珠穆沁旗| 乌拉特中旗| 四会市| 华宁县| 桃源县| 兴化市| 柳江县| 日照市| 沙田区| 容城县| 壶关县| 喜德县| 麻阳| 定结县| 博爱县| 安吉县| 双城市| 汾阳市| 普兰店市| 德化县| 嫩江县| 拜泉县| 乐东| 平乐县| 兖州市| 五大连池市| 漳州市| 邢台县|