最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java面試必備之JMM高并發(fā)編程詳解

 更新時間:2022年07月16日 15:58:49   作者:陳橘又青  
高并發(fā)(High?Cuncurrency)是互聯(lián)網(wǎng)分布式系統(tǒng)架構設計中必須考慮的因素之一,它通常是指,通過設計保證系統(tǒng)能夠同時并發(fā)處理很多請求

一、什么是JMM

JMM就是Java內存模型(java memory model)。因為在不同的硬件生產商和不同的操作系統(tǒng)下,內存的訪問有一定的差異,所以會造成相同的代碼運行在不同的系統(tǒng)上會出現(xiàn)各種問題。所以java內存模型(JMM)屏蔽掉各種硬件和操作系統(tǒng)的內存訪問差異,以實現(xiàn)讓java程序在各種平臺下都能達到一致的并發(fā)效果。

Java內存模型規(guī)定所有的變量都存儲在主內存中,包括實例變量,靜態(tài)變量,但是不包括局部變量和方法參數(shù)。每個線程都有自己的工作內存,線程的工作內存保存了該線程用到的變量和主內存的副本拷貝,線程對變量的操作都在工作內存中進行。線程不能直接讀寫主內存中的變量。

不同的線程之間也無法訪問對方工作內存中的變量。線程之間變量值的傳遞均需要通過主內存來完成。

每個線程的工作內存都是獨立的,線程操作數(shù)據(jù)只能在工作內存中進行,然后刷回到主存。這是 Java 內存模型定義的線程基本工作方式。

溫馨提醒一下,這里有些人會把Java內存模型誤解為Java內存結構,然后答到堆,棧,GC垃圾回收,最后和面試官想問的問題相差甚遠。實際上一般問到Java內存模型都是想問多線程,Java并發(fā)相關的問題。

二、JMM定義了什么

這個簡單,整個Java內存模型實際上是圍繞著三個特征建立起來的。分別是:原子性,可見性,有序性。這三個特征可謂是整個Java并發(fā)的基礎。

原子性

原子性指的是一個操作是不可分割,不可中斷的,一個線程在執(zhí)行時不會被其他線程干擾。

面試官拿筆寫了段代碼,下面這幾句代碼能保證原子性嗎?

int i = 2;
int j = i;
i++;
i = i + 1;

第一句是基本類型賦值操作,必定是原子性操作。

第二句先讀取i的值,再賦值到j,兩步操作,不能保證原子性。

第三和第四句其實是等效的,先讀取i的值,再+1,最后賦值到i,三步操作了,不能保證原子性。

JMM只能保證基本的原子性,如果要保證一個代碼塊的原子性,提供了monitorenter 和 moniterexit 兩個字節(jié)碼指令,也就是 synchronized 關鍵字。因此在 synchronized 塊之間的操作都是原子性的。

可見性

可見性指當一個線程修改共享變量的值,其他線程能夠立即知道被修改了。Java是利用volatile關鍵字來提供可見性的。 當變量被volatile修飾時,這個變量被修改后會立刻刷新到主內存,當其它線程需要讀取該變量時,會去主內存中讀取新值。而普通變量則不能保證這一點。

除了volatile關鍵字之外,final和synchronized也能實現(xiàn)可見性。

synchronized的原理是,在執(zhí)行完,進入unlock之前,必須將共享變量同步到主內存中。

final修飾的字段,一旦初始化完成,如果沒有對象逸出(指對象為初始化完成就可以被別的線程使用),那么對于其他線程都是可見的。

有序性

在Java中,可以使用synchronized或者volatile保證多線程之間操作的有序性。實現(xiàn)原理有些區(qū)別:

volatile關鍵字是使用內存屏障達到禁止指令重排序,以保證有序性。

synchronized的原理是,一個線程lock之后,必須unlock后,其他線程才可以重新lock,使得被synchronized包住的代碼塊在多線程之間是串行執(zhí)行的。

三、八種內存交互操作

內存交互操作有8種:

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

我再補充一下JMM對8種內存交互操作制定的規(guī)則吧:

  • 不允許read、load、store、write操作之一單獨出現(xiàn),也就是read操作后必須load,store操作后必須write。
  • 不允許線程丟棄他最近的assign操作,即工作內存中的變量數(shù)據(jù)改變了之后,必須告知主存。
  • 不允許線程將沒有assign的數(shù)據(jù)從工作內存同步到主內存。
  • 一個新的變量必須在主內存中誕生,不允許工作內存直接使用一個未被初始化的變量。就是對變量實施use、store操作之前,必須經過load和assign操作。
  • 一個變量同一時間只能有一個線程對其進行l(wèi)ock操作。多次lock之后,必須執(zhí)行相同次數(shù)unlock才可以解鎖。
  • 如果對一個變量進行l(wèi)ock操作,會清空所有工作內存中此變量的值。在執(zhí)行引擎使用這個變量前,必須重新load或assign操作初始化變量的值。
  • 如果一個變量沒有被lock,就不能對其進行unlock操作。也不能unlock一個被其他線程鎖住的變量。
  • 一個線程對一個變量進行unlock操作之前,必須先把此變量同步回主內存。

四、volatile關鍵字

很多并發(fā)編程都使用了volatile關鍵字,主要的作用包括兩點:

  • 保證線程間變量的可見性。
  • 禁止CPU進行指令重排序。

可見性

volatile修飾的變量,當一個線程改變了該變量的值,其他線程是立即可見的。普通變量則需要重新讀取才能獲得最新值。

volatile保證可見性的流程大概就是這個一個過程:

volatile一定能保證線程安全嗎

先說結論吧,volatile不能一定能保證線程安全。

怎么證明呢,我們看下面一段代碼的運行結果就知道了:

public class VolatileTest extends Thread {
private static volatile int count = 0;
public static void main(String[] args) throws Exception {
Vector<Thread> threads = new Vector<>();
for (int i = 0; i < 100; i++) {
VolatileTest thread = new VolatileTest();
threads.add(thread);
thread.start();
}
//等待子線程全部完成
for (Thread thread : threads) {
thread.join();
}
//輸出結果,正確結果應該是1000,實際卻是984
System.out.println(count);//984
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
//休眠500毫秒
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
count++;
}
}
}

為什么volatile不能保證線程安全?

很簡單呀,可見性不能保證操作的原子性,前面說過了count++不是原子性操作,會當做三步,先讀取count的值,然后+1,最后賦值回去count變量。需要保證線程安全的話,需要使用synchronized關鍵字或者lock鎖,給count++這段代碼上鎖:

private static synchronized void add() {
count++;
}

禁止指令重排序

首先要講一下as-if-serial語義,不管怎么重排序,(單線程)程序的執(zhí)行結果不能被改變。

為了使指令更加符合CPU的執(zhí)行特性,最大限度的發(fā)揮機器的性能,提高程序的執(zhí)行效率,只要程序的最終結果與它順序化情況的結果相等,那么指令的執(zhí)行順序可以與代碼邏輯順序不一致,這個過程就叫做指令的重排序。

重排序的種類分為三種,分別是:編譯器重排序,指令級并行的重排序,內存系統(tǒng)重排序。整個過程如下所示:

指令重排序在單線程是沒有問題的,不會影響執(zhí)行結果,而且還提高了性能。但是在多線程的環(huán)境下就不能保證一定不會影響執(zhí)行結果了。

所以在多線程環(huán)境下,就需要禁止指令重排序。

volatile關鍵字禁止指令重排序有兩層意思:

  • 當程序執(zhí)行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對后面的操作可見,在其后面的操作肯定還沒有進行。
  • 在進行指令優(yōu)化時,不能將在對volatile變量訪問的語句放在其后面執(zhí)行,也不能把volatile變量后面的語句放到其前面執(zhí)行。

下面舉個例子:

private static int a;//非volatile修飾變量
private static int b;//非volatile修飾變量
private static volatile int k;//volatile修飾變量
private void hello() {
a = 1; //語句1
b = 2; //語句2
k = 3; //語句3
a = 4; //語句4
b = 5; //語句5
//...
}

變量a,b是非volatile修飾的變量,k則使用volatile修飾。所以語句3不能放在語句1、2前,也不能放在語句4、5后。但是語句1、2的順序是不能保證的,同理,語句4、5也不能保證順序。

并且,執(zhí)行到語句3的時候,語句1,2是肯定執(zhí)行完畢的,而且語句1,2的執(zhí)行結果對于語句3,4,5是可見的。

volatile禁止指令重排序的原理

首先要講一下內存屏障,內存屏障可以分為以下幾類:

  • LoadLoad 屏障:對于這樣的語句Load1,LoadLoad,Load2。在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
  • StoreStore屏障:對于這樣的語句Store1, StoreStore, Store2,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對其它處理器可見。
  • LoadStore 屏障:對于這樣的語句Load1, LoadStore,Store2,在Store2及后續(xù)寫入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
  • StoreLoad 屏障:對于這樣的語句Store1, StoreLoad,Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫入對所有處理器可見。

在每個volatile讀操作后插入LoadLoad屏障,在讀操作后插入LoadStore屏障。

在每個volatile寫操作的前面插入一個StoreStore屏障,后面插入一個SotreLoad屏障。

大概的原理就是這樣。

五、總結

要學習并發(fā)編程,java內存模型是第一站了。原子性,有序性,可見性這三大特征幾乎貫穿了并發(fā)編程,可謂是基礎知識。對于后面要深入學習起到鋪墊作用。

在這篇文章中,如果面試的話,重點是Java內存模型(JMM)的工作方式,三大特征,還有volatile關鍵字。為什么喜歡問volatile關鍵字呢,因為volatile關鍵字可以扯出很多東西,比如可見性,有序性,還有內存屏障等等,可以一針見血地看出面試者的技術水平。

到此這篇關于Java面試必備之JMM高并發(fā)編程詳解的文章就介紹到這了,更多相關Java高并發(fā)編程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringBoot整合Redis時遇到連接問題的解決方法

    SpringBoot整合Redis時遇到連接問題的解決方法

    在使用Spring Boot整合Redis的過程中,經常會遇到連接問題,尤其是當Redis服務部署在遠程服務器上時,所以本文給大家介紹了SpringBoot整合Redis遇到連接問題的解決方法,需要的朋友可以參考下
    2024-11-11
  • 詳解Java中KMP算法的圖解與實現(xiàn)

    詳解Java中KMP算法的圖解與實現(xiàn)

    KMP算法是一種神奇的字符串匹配算法,在對超長字符串進行模板匹配的時候比暴力匹配法的效率會高不少。本文將利用圖解為大家詳細講解KMP算法的實現(xiàn),需要的可以參考一下
    2022-05-05
  • Sa-Token記住我模式實現(xiàn)七天免登錄

    Sa-Token記住我模式實現(xiàn)七天免登錄

    這篇文章主要為大家介紹了Sa-Token記住我模式實現(xiàn)七天免登錄示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • Spring Bean實例化實現(xiàn)過程解析

    Spring Bean實例化實現(xiàn)過程解析

    這篇文章主要介紹了Spring Bean實例化實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-02-02
  • Java常用類之字符串相關類使用詳解

    Java常用類之字符串相關類使用詳解

    String、StringBuilder、StringBuffer類是Java中常用的三個字符串相關類。本文將通過示例為大家講解一下他們的用法,需要的可以參考一下
    2022-08-08
  • java 實現(xiàn)取int型的第二個字節(jié)的數(shù)

    java 實現(xiàn)取int型的第二個字節(jié)的數(shù)

    這篇文章主要介紹了java 實現(xiàn)取int型的第二個字節(jié)的數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • 實例講解Java的設計模式編程中責任鏈模式的運用

    實例講解Java的設計模式編程中責任鏈模式的運用

    這篇文章主要介紹了Java的設計模式編程中責任鏈模式的運用,講解了通過條件判斷結構來分配不同對象的責任權限,需要的朋友可以參考下
    2016-02-02
  • mybatis-flex實現(xiàn)鏈式操作的示例代碼

    mybatis-flex實現(xiàn)鏈式操作的示例代碼

    MyBatis-Flex它提供了一種鏈式操作方式,本文主要介紹了mybatis-flex實現(xiàn)鏈式操作的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-06-06
  • java常見事件響應方法實例匯總

    java常見事件響應方法實例匯總

    這篇文章主要介紹了java常見事件響應方法,對于初學者有很好的參考借鑒價值,分享給大家,需要的朋友可以參考下
    2014-08-08
  • Java數(shù)組的基本操作方法整理

    Java數(shù)組的基本操作方法整理

    這篇文章主要介紹了Java數(shù)組的基本操作方法整理,是Java入門學習中的基礎知識,需要的朋友可以參考下
    2015-08-08

最新評論

朝阳市| 普陀区| 山阴县| 阿克陶县| 克什克腾旗| 岐山县| 拉萨市| 日照市| 聂拉木县| 金堂县| 西吉县| 颍上县| 龙里县| 辛集市| 巴林左旗| 徐汇区| 武义县| 东兰县| 赫章县| 满城县| 阿瓦提县| 娱乐| 贡嘎县| 泽库县| 鄯善县| 九寨沟县| 聂荣县| 江陵县| 上思县| 仁怀市| 宝应县| 兰溪市| 深泽县| 祥云县| 娄底市| 监利县| 芒康县| 遂川县| 永清县| 宜都市| 兴海县|