1. 程式人生 > >volatile關鍵字的介紹和使用

volatile關鍵字的介紹和使用

關鍵字volatile的主要作用是使變數在過個執行緒中可見

1、假設volatile不存在我們將會面對的問題

public class PrintString implements Runnable
{

    private boolean isContinuePrint = true;

    public boolean isContinuePrint() {
        return isContinuePrint;
    }

    public void setContinuePrint(boolean isContinuePrint) {
        this
.isContinuePrint = isContinuePrint; } public void printStringMethod() { try { while(isContinuePrint) { System.out.println("run printStringMethod threadName = " + Thread.currentThread().getName()); Thread.sleep(1000); } } catch
(InterruptedException exception) { exception.printStackTrace(); } } @Override public void run() { printStringMethod(); } }
public class Test {

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        try
{ PrintString mPrintString = new PrintString(); Thread mThread = new Thread(mPrintString); mThread.start(); Thread.sleep(1000); mPrintString.setContinuePrint(false);//在-server模式下,這個操作修改的是公共堆疊中的值,而執行緒的私有堆疊並沒有修改 } catch(InterruptedException e) { e.printStackTrace(); } } }

  當JVM 在-server模式下並不會停止PrintString的method,因為在-server的模式下存在一個公共堆疊和執行緒私有堆疊的概念。我們在呼叫setConinuePrinter(false)修改的是公共堆疊中的值,並不會影響到執行緒的私有堆疊。

  此時我們需要引入volatile關鍵字,作用是強制執行緒去公共堆疊中訪問isContinuePrint的值。

  使用volatile關鍵字增加了例項變數在多個執行緒之間的可見性,但volatile關鍵字有一個致命的缺陷是不支援原子性

  synchronized與volatile關鍵字之間的比較

  • 關鍵字volatile是執行緒同步的輕量實現,所以volatile關鍵字效能比synchronized好。volatile只能修飾變數,synchronized可以修飾方法,程式碼塊
  • volatile不會阻塞執行緒,synchronized會阻塞執行緒
  • volatile能保證資料的可見性,不保證原子性,synchronized可以保證原子性,可以間接保證可見性,它會將公共記憶體和私有記憶體的資料做同步處理。
  • volatile解決的是變數在多個執行緒之間的可見性,synchronized解決的是多個執行緒之間訪問資源的同步性

  請記住Java的同步機制都是圍繞兩點:原子性執行緒之間的可見性.只有滿足了這兩點才能稱得上是同步的。Java中的synchronized和volatile兩個關鍵字分別執行的是原子性和執行緒之間的可見性。

volatile 並非原子性
  看一個例子

package com.sophia.demo;

public class MyThread extends Thread
{
    volatile public static int COUNT;

    private static void addCount()
    {
        for (int i = 0; i < 100; i++) 
        {
            COUNT++;
        }
        System.out.println("count = " + COUNT);
    }

    @Override
    public void run()
    {
        addCount();
    }
}
package com.sophia.demo;

public class StringTest {

    public static void main(String[] args)
    {
        MyThread[] myThreads = new MyThread[100];
        for (int i = 0; i < myThreads.length; i++)
        {
            myThreads[i] = new MyThread();
        }

        for (MyThread myThread : myThreads)
        {
            if (null != myThread)
            {
                myThread.start();
            }
        }
    }
}

輸出的結果並不如我們想象中的那樣子遞增

count = 200
count = 300
count = 200

  仔細觀察輸出結果中我們看到有這樣子一段結果,當我們在addCount()方法中加上synchronized方法就能達到同步的效果。此時就沒必要使用volatile關鍵字了。
首先我們來分析一個常見的表示式i++即i = i + 1;這個操作不是原子性也就是非執行緒安全的,分解的步驟如下

  1. 從記憶體中取出i的值
  2. 計算i的值
  3. 將i的值寫入記憶體中

  假設在執行第2步的時候,有其他執行緒在修改i的值,這個時候就會出現髒資料,解決的辦法就是使用synchronized關鍵字。所以說volatile並不處理資料的原子性
  詳細解釋一下volatile出現執行緒不安全的原因:

  1. read和load過程:從主存中複製變數到當前執行緒的工作記憶體中
  2. use和assign過程:執行程式碼,更改變數的值
  3. store和write過程:儲存更改後的變數,並寫入主存。

  在多執行緒換種中,use和assign是多次出現的,但這一操作並不是原子性,也就是在read和load之後,如果主記憶體count變數傳送修改之後,執行緒工作記憶體中的值由於已經載入,不會產生對應的變化,也就是私有記憶體和公共記憶體的值不一樣。此時其他執行緒在去取公共記憶體的值就會造成偏差。導致執行緒不安全。所以多個執行緒訪問同一個變數加鎖才是最安全的操作。

  Java提供了一個執行緒安全的Integer類,AtomicInteger,原子操作是不可分割的整體,沒有其他執行緒能夠終端或檢查正在原子操作中的變數,可以在沒有鎖的情況下做到執行緒安全