線程安全&Java內存模型

Java內存模型

Java內存模型(JMM)主要目標是定義多線程的狀況下線程訪問變量的規則java

JMM規定線程之間的共享變量存儲在主內存中,每一個線程都有一個本地內存(工做內存),本地內存存儲了共享變量的副本。安全

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTYwOTIxMTgyMzM3OTA0

關於線程安全

  • 什麼是線程安全問題?多線程

    多個線程同時共享同一個全局變量的操做時候,可能會受到其餘線程的干擾,致使數據髒讀。(數據一致性問題)性能

  • 如何解決線程安全問題?線程

    核心思想:在同一時刻,只能有一個線程執行。對象

    經過加鎖使線程更加安全,也使程序的執行效率更低。blog

  • 衡量線程安全的3個要素:排序

    • 原子性:一個操做或者多個操做要麼所有執行,要麼都不執行
    • 可見性:多個線程訪問同一變量,一個線程修改了變量的值,其餘線程能夠當即看到修改的值
    • 有序性:程序按照代碼的順序前後執行(與指令重排有關)

Volatile關鍵字

volatile是一種輕量級的同步機制,能夠保證可見性【及時將修改的變量刷新到主內存中】,但不能保證原子性,而且禁止重排序。遞歸

volatile在多線程下的適用場景:一寫多讀事務

volatile如何保證內存可見性?

當一個線程對volatile修飾的變量進行寫操做時,該線程中的本地內存的變量會被馬上刷新到主內存中。

當一個線程對volatile修飾的變量進行讀操做時,該線程直接讀取主內存的變量。

volatile可否保證線程安全?

不能,保證線程安全須要同時具有原子性,可見性和有序性。而volatile只能保證可見性和有序性,沒法保證原子性。

Synchronized關鍵字

核心思想:在多線程執行同一個方法時,只有獲取到鎖,才能進入方法裏面執行

使用方式:

  • 修飾一個類:其做用的範圍是synchronized後面括號括起來的部分,做用的對象是這個類的全部對象
  • 修飾一個方法:被修飾的方法稱爲同步方法,其做用的範圍是整個方法,做用的對象是調用這個方法的對象
  • 修飾一個靜態的方法:其做用的範圍是整個方法,做用的對象是這個類的全部對象
  • 修飾一個代碼塊:被修飾的代碼塊稱爲同步語句塊,其做用範圍是大括號{}括起來的代碼塊,做用的對象是調用這個代碼塊的對象

鎖的分類

輕量級鎖&重量級鎖

輕量級鎖:手動上鎖解鎖,擴展性強。表明:Lock

重量級鎖:自動上鎖解鎖,封裝程度高。表明:Synchronized

可重入鎖&不可重入鎖

可重入鎖(遞歸鎖):當一個線程已經獲取到鎖後,再次請求該鎖,就可直接獲取。(鎖的傳遞,鎖的嵌套)表明:Synchronized,Lock

鎖的可重入性避免了大部分死鎖狀況的產生

不可重入鎖:不具有傳遞性

讀寫鎖

ReentrantReadWriteLock

相對Synchronized效率更高,但在多線程狀況下,只支持讀讀共存,不支持讀寫,寫寫。

樂觀鎖與悲觀鎖

樂觀鎖(適合多讀場景)

  • 思想:認爲不會發生線程衝突(本質上是沒有鎖的)

  • 執行流程,先讀取數據,而後在更新前檢查在讀取至更新這段時間數據是否被修改

    • 未修改:直接更新數據
    • 已修改:從新讀取,再次提交更新(或者放棄操做)

爲何樂觀鎖適合多讀場景?

樂觀鎖是一種更新前的檢查機制,相對於悲觀鎖來講在多讀場景下能夠減小鎖的性能開銷,對於多寫場景,樂觀鎖會一直進入已修改,從新讀取,再次提交的循環,反而帶來更多的資源消耗。

悲觀鎖(適合多寫場景)

  • 思想:認爲必定會發生線程衝突

  • 執行流程:讀取數據的時候上鎖(其餘用戶沒法讀取),直到本次數據更新完成纔會釋放鎖。在多寫場景下,能保證較高的數據一致性。

【總的來講,樂觀鎖回滾重試,悲觀鎖阻塞事務】

CAS無鎖機制

原子類:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference可保證線程安全,底層使用CAS無鎖機制

CAS:Compare and Swap,比較再交換,屬於樂觀鎖的一種

  • CAS原理

    CAS包含3個參數,CAS(V,E,N),V:主內存的變量值,E:本地內存修改前的值,N:本地內存修改後的值

    比較主內存的值和本地內存修改前的值是否一致,若一致,將修改後的值刷新到主內存,若不一致,當前線程放棄更新,將主內存數據刷新到本地內存,再次重試。

  • 優勢:非阻塞,不會發生死鎖狀況,效率更高

  • 缺點:ABA問題(能夠經過加入版本號來區分變量是否被修改)

相關文章
相關標籤/搜索