歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java多線程之內存可見性

Java多線程之內存可見性

日期:2017/3/1 9:05:23   编辑:Linux編程

volatile不需要加鎖,比synchronized更輕量級,不會阻塞線程,所以volatile執行效率更高;
從內存可見性角度看,volatile讀相當於加鎖,volatile寫相當於解鎖;
synchronized既能保證可見性,又能保證原子性,而volatile只能保證可見性,無法保證原子性;

synchronized能夠保證鎖內操作的原子性(同步),並且也具備內存可見性的特性

JMM(Java內存管理)關於synchronized的兩條規定:

1) 線程解鎖前,必須把共享變量的最新值刷新到主內存中
2) 線程加鎖時,將清空工作內存中的共享變量的值,
從而使用共享變量時需要從主內存中重新讀取最新的值(加鎖解鎖需要的都是同一把鎖)
(ps.線程解鎖前對共享變量的修改在下次加鎖時對其他線程可見)

.

線程執行互斥代碼的過程:

1.獲得互斥鎖
2.清空工作內存
3.從主內存拷貝變量的最新副本到工作內存
4.執行代碼(可能代碼會改變共享變量的值)
5.將更改後的共享變量的值刷新到主內存中
6.釋放互斥鎖

public class Demo {
    //共享變量
    private boolean ready = false;
    private int result = 0;
    private int number = 1;

    //可以猜想一下,讀寫操作的步驟的順序,result可能是6或者0
    //寫操作
    public void write() {
        ready = true;           //寫操作第一步
        number = 2;             //寫操作第二步
    }
    //讀操作
    public void read() {
        if(ready) {             //讀操作第一步
            result = number*3;  //讀操作第二步
        }
        System.out.println("result的值為:"+result);
    }

    //內部線程類
    private class ReadWriteThread extends Thread {
        //根據構造方法中傳入的flag參數。確定線程執行讀操作還是寫操作
        private boolean flag;
        public ReadWriteThread(boolean flag) {
            this.flag = flag;
        }
        public void run() {
            if(flag) {
                //構造方法中傳入true,執行寫操作
                write();
            } else {
                //構造方法中傳入flase,執行讀操作
                read();
            }
        }
    }

    public static void main(String args[]) {
        Demo demo = new Demo();
        //啟動線程執行寫操作
        demo.new ReadWriteThread(true).start();
        //啟動線程執行讀操作
        demo.new ReadWriteThread(false).start();
    }
}
/*
導致共享變量在線程間不可見的原因:
1.線程的交叉執行
2.重排序結合線程交叉執行
3.共享變量更新後的值沒有在工作內存與主內存之間及時更新
*/

在讀寫操作方法加入synchronized關鍵字,使其變成安全的代碼:

public synchronized void write() {
    ready = true;
    number = 2;
}
public synchronized void read() {
    if(ready) {
        result = number*3;
    }
    System.out.println("result的值為:"+result);
}
/*
synchronized相當於加了一把鎖,鎖內部的代碼在一段時間內只能由一個線程執行,除非釋放鎖
synchronized其原子性,避免了線程的交叉執行的發生;
由於已經避免了線程的交叉執行,所以無論鎖內部怎麼重排序,都不會對結果造成影響;
synchronized其可見性的特性,可以避免共享變量未及時更新;
*/

還有一種特殊的情況,就是還未執行寫操作,先執行讀操作,
這種情況也是result為0,但是由於不是因為線程交叉執行而導致的,所以使用synchronized關鍵字沒有作用
避免這種情況的話,可以使用sleep()使其休眠,代碼如下:

//修改代碼,休眠操作
public static void main(String args[]) {
    Demo demo = new Demo();
    //啟動線程執行寫操作
    demo.new ReadWriteThread(true).start();
    try {
        Thread.sleep(1000);
    } catch(InterruptedException e) {
        e.printStackTrace();
    }
    //啟動線程執行讀操作
    demo.new ReadWriteThread(false).start();
}

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2017-02/141093p2.htm

Copyright © Linux教程網 All Rights Reserved