Java併發之原子性、有序性、可見性

原子性

​ 原子性指的是一個或者多個操做在 CPU 執行的過程當中不被中斷的特性java

線程切換 帶來的原子性問題緩存

Java 併發程序都是基於多線程的,操做系統爲了充分利用CPU的資源,將CPU分紅若干個時間片,在多線程環境下,線程會被操做系統調度進行任務切換。性能優化

image-20190304155302961

爲了直觀的瞭解什麼是原子性,咱們看下下面哪些操做是原子性操做多線程

int count = 0; //1
count++;       //2
int a = count; //3
複製代碼

上面展現語句中,除了語句1是原子操做,其它兩個語句都不是原子性操做,下面咱們來分析一下語句2併發

其實語句2在執行的時候,包含三個指令操做性能

  • 指令 1:首先,須要把變量 count 從內存加載到 CPU的寄存器
  • 指令 2:以後,在寄存器中執行 +1 操做;
  • 指令 3:最後,將結果寫入內存

對於上面的三條指令來講,若是線程 A 在指令 1 執行完後作線程切換,線程 A 和線程 B 按照下圖的序列執行,那麼咱們會發現兩個線程都執行了 count+=1 的操做,可是獲得的結果不是咱們指望的 2,而是 1。優化

image-20190304163607329

操做系統作任務切換,能夠發生在任何一條CPU 指令執行完spa

有序性

​ 有序性指的是程序按照代碼的前後順序執行操作系統

**編譯優化 **帶來的有序性問題線程

爲了性能優化,編譯器和處理器會進行指令重排序,有時候會改變程序中語句的前後順序,好比程序:

a = 5;     //1
b = 20;    //2
c = a + b; //3
複製代碼

編譯器優化後可能變成

b = 20;    //1
a = 5;     //2
c = a + b; //3
複製代碼

在這個例子中,編譯器調整了語句的順序,可是不影響程序的最終結果

synchronized(具備有序性、原子性、可見性)表示鎖在同一時刻只能由一個線程進行獲取,當鎖被佔用後,其餘線程只能等待。

在單例模式的實現上有一種雙重檢驗鎖定的方式(Double-checked Locking)

public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}
複製代碼

咱們先看 instance = new Singleton() 的未被編譯器優化的操做

  • 指令 1:分配一塊內存 M;

  • 指令 2:在內存 M 上初始化 Singleton 對象;

  • 指令 3:而後 M 的地址賦值給 instance 變量。

編譯器優化後的操做指令

  • 指令 1:分配一塊內存 M;

  • 指令 2:將 M 的地址賦值給 instance 變量;

  • 指令 3:而後在內存 M 上初始化 Singleton 對象。

如今有A,B兩個線程,咱們假設線程A先執行getInstance()方法,當執行編譯器優化後的操做指令2時(此時候未完成對象的初始化),這時候發生了線程切換,那麼線程B進入,恰好執行到第一次判斷 instance==null會發現instance不等於null了,因此直接返回instance,而此時的 instance 是沒有初始化過的。

image-20190304173844373

現行的比較通用的作法就是採用靜態內部類的方式來實現

public class SingletonDemo {
    private SingletonDemo() {
    }
    private static class SingletonDemoHandler{
        private static SingletonDemo instance = new SingletonDemo();
    }
    public static SingletonDemo getInstance() {
        return SingletonDemoHandler.instance;
    }
}
複製代碼
可見性

​ 可見性指的是當一個線程修改了共享變量後,其餘線程可以當即得知這個修改

緩存 致使的可見性問題

首先咱們來看一下Java內存模型(JMM)

image-20190304183341997

  • 咱們定義的全部變量都儲存在主內存
  • 每一個線程都有本身獨立的工做內存,裏面保存該線程使用到的變量的副本(主內存中該變量的一份拷貝)
  • 線程對共享變量全部的操做都必須在本身的工做內存中進行,不能直接從主內存中讀寫(不能越級)
  • 不一樣線程之間也沒法直接訪問其餘線程的工做內存中的變量,線程間變量值的傳遞須要經過主內存來進行。(同級不能相互訪問)

共享變量可見性的實現原理:

線程1對共享變量的修改要被線程2及時看到的話,要通過以下步驟:

  1. 把工做內存1中更新的變量值刷新到主內存
  2. 把主內存中的變量的值更新到工做內存2中

能夠使用 synchronizedvolatilefinal 來保證可見性

歡迎關注公衆號

image-20190304183341997
相關文章
相關標籤/搜索