volatile 與 synchronize 詳解

Woo_home發表於2020-01-23

Java支援多個執行緒同時訪問一個物件或者物件的成員變數,由於每個執行緒可以擁有這個變數的拷貝(雖然物件以及成員變數分配的記憶體是在共享記憶體中的,但是每個執行的執行緒還是可以擁有一份拷貝,這樣做的目的是加速程式的執行,這是現代多核處理器的一個顯著特性),所以程式在執行過程中,一個執行緒看到的變數並不一定是最新的。

volatile

volatile的應用

在多執行緒併發程式設計中 synchronized 和 volatile 都扮演著重要的角色,volatile 是一個輕量級的 synchronized ,它在多處理器開發中保證了共享變數的 可見性。可見性的意思是當一個執行緒修改一個共享變數的時候,另一個執行緒能讀取到這個共享變數被修改後的值。如果 volatile 使用恰當的話,它比 synchronized的使用和執行成本更低,因為volatile不會引起執行緒上下文的切換和排程

volatile的定義與實現原理

Java程式語言允許執行緒訪問共享變數,為了確保共享變數能被準確和一致性地更新,執行緒應該確保通過排他鎖來單獨獲取這個變數。Java提供的volatile在某些情況下比鎖要方便。如果一個欄位被宣告為volatile,那麼Java模型確保所有的執行緒看到這個變數的值是一致的

volatile的兩條實現原則
  • Lock字首指令會引起處理器快取回寫到記憶體
  • 一個處理器的快取回寫到記憶體會導致其他的處理器的快取無效

synchronized

synchronized關鍵字可以修飾方法或者以同步塊的形式來進行使用,它主要確保多個執行緒在同一個時刻,只能有一個執行緒處於方法或者同步塊中,它保證了執行緒對變數訪問的可見性和排他性

synchronized的實現原理與應用

在多執行緒併發程式設計中 synchronized 一直是元老級的角色,很多人都會直呼它為重量級鎖。但是,隨著Java SE 1.6對 synchronized進行了各種優化之後,有些情況下synchronized並沒有那麼重了

synchronized如何實現同步?

synchronized實現同步的基礎:Java中的每一個物件都可以作為鎖。具體的表現形式有以下三種:

  • 對於普通同步方法,鎖是當前例項物件
  • 對於靜態同步方法,鎖是當前類的Class物件
  • 對於同步方法塊,鎖是synchronized括號裡配置的物件

當一個執行緒試圖訪問同步程式碼塊時,必須先獲取到鎖,退出或丟擲異常時必須釋放鎖

volatile 與 synchronized 的區別?

volatile synchronized
修飾 只能用於修飾變數 可以用於修飾方法、程式碼塊
執行緒阻塞 不會發生執行緒阻塞 會發生阻塞
原子性 不能保證變數的原子性 可以保證變數原子性
可見性 可以保證變數線上程之間訪問資源的可見性 可以間接保證可見性,因為它會將私有記憶體中和公共記憶體中的資料做同步
同步性 能保證變數在私有記憶體和主記憶體間的同步 synchronize是多執行緒之間訪問資源的同步性
  • volatile是執行緒同步的輕量級實現,所以volatile的效能要比synchronize好;隨著jdk技術的發展,synchronize在執行效率上會得到較大提升,所以synchronize在專案過程中還是較為常見的
  • 對於volatile修飾的變數,可以解決變數讀時可見性問題,無法保證原子性。對於多執行緒訪問同一個例項變數還是需要加鎖同步

在多執行緒定義中,volatile關鍵字主要是在屬性上使用的,表示此屬性為直接資料操作,而不進行副本的拷貝處理。這樣的話在一些書上就將其錯誤的理解為同步屬性了。
在這裡插入圖片描述

package com.java.springtest.test;

/**
 * @author Woo_home
 * @create by 2020/1/20
 */
public class ThreadDemo {
    public static void main(String[] args) throws Exception{
        ThreadShop shopA = new ThreadShop();
        ThreadShop shopB = new ThreadShop();
        ThreadShop shopC = new ThreadShop();
        new Thread(shopA,"A 店鋪").start();
        new Thread(shopB,"B 店鋪").start();
        new Thread(shopC,"C 店鋪").start();
    }
}

class ThreadShop implements Runnable {

    private volatile int product = 5; // 直接記憶體操作

    @Override
    public void run() {
        synchronized (this) {
            while (this.product > 0) {
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "商品處理,product = " + this.product--);
            }
        }
    }
}

在這裡插入圖片描述
在正常進行變數處理的時候往往會經歷如下的幾個步驟:

  • 獲取變數原有的資料內容副本;
  • 利用副本為變數進行數學計算;
  • 將計算後的變數,儲存到原始空間之中;
    而如果一個屬性上追加了volatile關鍵字,表示的就是不使用副本,而是直接操作原始變數,相當於節約了拷貝副本、重新儲存的步驟

面試題: 請解釋volatile與synchronized的區別?
1)volatile主要在屬性上使用,而synchronized是在程式碼塊與方法上使用;
2)volatile無法描述同步的處理,它只是一種直接記憶體的處理,避免了副本的操作,而synchronized是實現同步的。

相關文章