1. 程式人生 > >synchronized關鍵字與volatile原理淺析

synchronized關鍵字與volatile原理淺析

synchronized關鍵字與volatile關鍵字可以說是Java多執行緒的基礎,無論是各種同步鎖,還是我們所遇到的執行緒安全問題,都與這兩個關鍵字有聯絡。

Synchronized

Synchronized關鍵字我們經常用來執行執行緒間的互斥操作,使用示例如下:

    int x;
    public synchronized void setX(int x) {
        this.x = x;
    }

示例十分地簡單,如果兩個執行緒訪問這個函式,他們是互斥的訪問的,Synchronized關鍵字在這裡對函式進行了類似原子操作的包裝,在JMM(Java memory model)裡面,Synchronized操作是這樣的,當執行緒允許到使用Synchronized關鍵字修飾的程式碼時,它會先獲取到Synchronized函式所對應的Monitor物件,如果兩個執行緒獲取到的需要獲取同一個Monitor物件時,他們之間就會互斥。示例如下:

    int x;
    int y;
    public synchronized void setX(int x) {
        this.x = x;
    }

    public synchronized void setY(int y) {
        this.y = y;
    }

在這個示例中,如果執行緒A在執行setX函式,而執行緒B在執行setY函式,他們之間也是互斥地進行的,因為他們所需要獲取到的Monitor是同一個。

那有沒有辦法讓兩條執行緒不互斥地進行呢?因為兩個函式當中資源並沒有搶佔的情況,所以,我們可以這樣解決:

    int
x; int y; private final Object object1 = new Object(); private final Object object2 = new Object(); public void setX(int x) { synchronized (object1) { this.x = x; } } public synchronized void setY(int y) { synchronized (object2) { this
.y = y; } }

這樣A執行緒訪問setX與B執行緒訪問setY是不會互斥的,因為他們所需要獲得的Monitor不是同一個。

Synchronize關鍵字與工作記憶體和共享記憶體

從執行緒角度來說,記憶體可分為兩種,一種是執行緒內的工作記憶體,另一種是執行緒間的共享記憶體,當使用Synchronized的時候,它主要做了三件事

  1. 在執行之前,到共享記憶體讀取到所需變數
  2. 讀取變數之後,其他執行緒無法再對此變數進行操作(其他執行緒將採取自旋或阻塞的方式等待)
  3. 函式結束之後,將該變數的值重新整理並解除鎖定
鎖的四種狀態

在Synchronizationd中的鎖是隱式鎖,在Java對它進行優化之後,它是會進行自動升級的,升級的階段有四個,分別如下:

  • 無鎖
  • 偏向鎖
  • 輕量級鎖
  • 重量級鎖

其中偏向鎖是Synchronizationd關鍵字所做的優化,它將獲取鎖的代價降到了最低,但是隻適合單執行緒使用,當發生競爭時,偏向鎖會升級成輕量級鎖,這時如果鎖已經被持有,則執行緒就會採取自旋的方式等待(自旋其實就是迴圈),如果競爭再升級,就會升級成重量級鎖,這時如果再要獲取鎖,就會採取阻塞的方式等待。

volatile

volatile關鍵字所做的事情其實就是在改變volatile所修飾的變數時,它是會強制重新整理到共享記憶體的,我們可以這樣理解,volatile的每一步操作都和包裹在synchronized程式碼塊中一樣,但是!特別需要注意的是,這裡的每一步並不是指的一行程式碼,而是指的每一步原子操作。