摘要:多線程是一個(gè)龐大的知識(shí)體系,這里對(duì)其中的進(jìn)行一個(gè)總結(jié),理清他的來龍去脈。替換重量級(jí)鎖在中又稱為重量級(jí)鎖,能夠保重的幾大特性一致性,原子性,可見性。
Java多線程是一個(gè)龐大的知識(shí)體系,這里對(duì)其中的volatile進(jìn)行一個(gè)總結(jié),理清他的來龍去脈。
CPU緩存要搞懂volatile,首先得了解CPU在運(yùn)行過程中的存儲(chǔ)是如何處理的,其結(jié)構(gòu)如圖
CPU會(huì)把一些經(jīng)常使用的數(shù)據(jù)緩存在cache中,避免每次都去訪問較慢的memory。在單線程環(huán)境下,如果一個(gè)變量的修改都在cache中,自然不會(huì)有什么問題,可是在多線程環(huán)境中就可能是下面這個(gè)圖的示意圖(單核另當(dāng)別論)
CPU1 修改了一個(gè)變量a存入cache1,但是CPU2 在cache2中看到的a任然是之前的a,所以造成CPU1修改失效,我們來看看示例代碼:
import java.util.concurrent.TimeUnit; public class Counter { private static boolean stop ; //private static volatile boolean stop ; public static void main(String[] args) throws Exception { Thread t = new Thread(new Runnable() { @Override public void run() { int i = 0; while (!stop) { i++; } } } ); t.start(); TimeUnit.MILLISECONDS .sleep(5); stop = true; } }
在我的4核筆記本上運(yùn)行結(jié)果:
就一直運(yùn)行著,沒有停止(需要手工停止),這說明在主線程中修改的stop變量后,線程t沒有讀取到最新的stop的值,還一直是false。
volatile原理volatile的原理就是,如果CPU1修改了一個(gè)變量a,不僅要修改自身的cache,還要同步到memory中去,并且使CPU2的cache中的變量a失效,如果CPU2要讀取a,那么就必須到memory中去讀取,這樣就保證了不同的線程之間對(duì)于a的可見性,亦即,無論哪個(gè)線程,隨時(shí)都能獲得變量a最新的最新值。
我們來看看示例代碼:
import java.util.concurrent.TimeUnit; public class Counter { //private static boolean stop ; private static volatile boolean stop ; public static void main(String[] args) throws Exception { Thread t = new Thread(new Runnable() { @Override public void run() { int i = 0; while (!stop) { i++; } } } ); t.start(); TimeUnit.MILLISECONDS .sleep(5); stop = true; } }
在我的4核筆記本上運(yùn)行結(jié)果:
很快程序就結(jié)束了,說明線程t讀到了經(jīng)主線程修改后的stop變量,然后就停止了。
(例子源于《effective Java》)
volatile使用場(chǎng)景 狀態(tài)標(biāo)志就像上面的代碼里,把簡(jiǎn)單地volatile變量作為狀態(tài)標(biāo)志,來達(dá)成線程之間通訊的目的,省去了用synchronized還要wait,notify或者interrupt的編碼麻煩。
替換重量級(jí)鎖在Java中synchronized 又稱為重量級(jí)鎖,能夠保重JMM的幾大特性:一致性,原子性,可見性。但是由于使用了鎖操作,在一定程度上會(huì)有更高的性能消耗(鎖的線程互斥性亦即資源消耗)。而volatile能提供可見性,原子性(單個(gè)變量操作,不是a++這種符合操作),所以在讀寫上,可以用volatile來替換synchronized的讀操作,而寫操作仍然有synchronized實(shí)現(xiàn),能取得更好的性能。
import java.util.ArrayList; import java.util.List; public class Counter1 { private class Count11 { private int value; public synchronized int getValue() { return value; } public synchronized int increment() { return value++; } } // private class Count11 { // private volatile int value=0; // int getValue() { return value; } // synchronized int increment() { return value++; } // } public static void main(String[] args) throws Exception { Counter1.Count11 count11 = new Counter1().new Count11(); ListthreadArrayList = new ArrayList<>(); final int[] a = {0}; Long allTime = 0l; long startTime = System.currentTimeMillis(); for (int i = 0; i < 4; i++) { Thread t = new Thread(() -> { int b = 0; for (int j = 0; j < 10000; j++) { count11.increment(); a[0] = count11.getValue(); } for (int j = 0; j < 10000; j++) { b++; a[0] = count11.getValue(); } }); t.start(); threadArrayList.add(t); } for (Thread t : threadArrayList) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } long endTime = System.currentTimeMillis(); allTime = ((endTime - startTime)); System.out.println("result: " + a[0] + ", average time: " + (allTime) + "ms"); } }
volatile優(yōu)化結(jié)果:
result: 40000, average time: 124ms result: 40000, average time: 133ms result: 40000, average time: 141ms result: 40000, average time: 112ms result: 40000, average time: 123ms result: 40000, average time: 143ms result: 40000, average time: 120ms result: 40000, average time: 120ms
未優(yōu)化結(jié)果:
result: 40000, average time: 144ms result: 40000, average time: 150ms result: 40000, average time: 149ms result: 40000, average time: 165ms result: 40000, average time: 134ms result: 40000, average time: 132ms result: 40000, average time: 157ms result: 40000, average time: 138ms result: 40000, average time: 158ms
可見使用volatile過后效果的確優(yōu)于只使用synchronized的性能,不過試驗(yàn)中發(fā)現(xiàn)有個(gè)閾值,如果讀取修改次數(shù)較小,比如1000以內(nèi),只使用synchronized效果略好,存取次數(shù)變大以后 volatile的優(yōu)勢(shì)才慢慢體現(xiàn)出來(次數(shù)達(dá)到10000的話,差距就在60ms左右)。
待挖掘還有很多用法,在將來的學(xué)習(xí)中,不斷總結(jié)與挖掘。
聯(lián)想無論處于應(yīng)用的哪一層,優(yōu)化的思路都是可以相互借鑒的,比如我們做一個(gè)服務(wù)集群,如果每一個(gè)節(jié)點(diǎn)都要保存所有用戶的session,就很難使得session同步,我們就可以借鑒volatile這種思路,在集群之上搞一個(gè)調(diào)度器,如果某一個(gè)節(jié)點(diǎn)修改了一個(gè)用戶session,就報(bào)告給調(diào)度器,然后調(diào)度器通知其他所有節(jié)點(diǎn)修改該用戶session。而一般情況下,數(shù)據(jù)的讀寫比都比較高,所以這樣做就能到達(dá)一個(gè)很好的性能。
注意事項(xiàng)引用類型的volatile只在引用本身發(fā)生變化時(shí)具有可見性,其引用的對(duì)象的元素發(fā)生變化時(shí)不具有可見性
歡迎訪問我的個(gè)人主頁 mageek(mageek.cn)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/67040.html
摘要:閱讀本文約分鐘上一次我們說到互斥代碼的實(shí)現(xiàn)過程,如果有忘記或不清楚的可以去上篇看看。貓說多線程之內(nèi)存可見性上篇今天我們了解下重排序。 閱讀本文約3分鐘 上一次我們說到synchronized互斥代碼的實(shí)現(xiàn)過程,如果有忘記或不清楚的可以去上篇看看?!綣ava貓說】Java多線程之內(nèi)存可見性(上篇) 今天我們了解下重排序。 其使代碼書寫的順序與實(shí)現(xiàn)執(zhí)行的順序不同,指令重排序是編譯器或處理...
時(shí)間:2017年07月09日星期日說明:本文部分內(nèi)容均來自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)源碼:無學(xué)習(xí)源碼:https://github.com/zccodere/s... 第一章:課程簡(jiǎn)介 1-1 課程簡(jiǎn)介 課程目標(biāo)和學(xué)習(xí)內(nèi)容 共享變量在線程間的可見性 synchronized實(shí)現(xiàn)可見性 volatile實(shí)現(xiàn)可見性 指令重排序 as-if-seria...
摘要:前半句是指線程內(nèi)表現(xiàn)為串行的語義,后半句是指指令重排序現(xiàn)象和工作內(nèi)存和主內(nèi)存同步延遲現(xiàn)象。關(guān)于內(nèi)存模型的講解請(qǐng)參考死磕同步系列之。目前國(guó)內(nèi)市面上的關(guān)于內(nèi)存屏障的講解基本不會(huì)超過這三篇文章,包括相關(guān)書籍中的介紹。問題 (1)volatile是如何保證可見性的? (2)volatile是如何禁止重排序的? (3)volatile的實(shí)現(xiàn)原理? (4)volatile的缺陷? 簡(jiǎn)介 volatile...
摘要:前半句是指線程內(nèi)表現(xiàn)為串行的語義,后半句是指指令重排序現(xiàn)象和工作內(nèi)存和主內(nèi)存同步延遲現(xiàn)象。關(guān)于內(nèi)存模型的講解請(qǐng)參考死磕同步系列之。目前國(guó)內(nèi)市面上的關(guān)于內(nèi)存屏障的講解基本不會(huì)超過這三篇文章,包括相關(guān)書籍中的介紹。問題 (1)volatile是如何保證可見性的? (2)volatile是如何禁止重排序的? (3)volatile的實(shí)現(xiàn)原理? (4)volatile的缺陷? 簡(jiǎn)介 volatile...
摘要:前半句是指線程內(nèi)表現(xiàn)為串行的語義,后半句是指指令重排序現(xiàn)象和工作內(nèi)存和主內(nèi)存同步延遲現(xiàn)象。關(guān)于內(nèi)存模型的講解請(qǐng)參考死磕同步系列之。目前國(guó)內(nèi)市面上的關(guān)于內(nèi)存屏障的講解基本不會(huì)超過這三篇文章,包括相關(guān)書籍中的介紹。問題 (1)volatile是如何保證可見性的? (2)volatile是如何禁止重排序的? (3)volatile的實(shí)現(xiàn)原理? (4)volatile的缺陷? 簡(jiǎn)介 volatile...
閱讀 3749·2021-11-24 10:46
閱讀 1713·2021-11-15 11:38
閱讀 3770·2021-11-15 11:37
閱讀 3491·2021-10-27 14:19
閱讀 1950·2021-09-03 10:36
閱讀 2000·2021-08-16 11:02
閱讀 3006·2019-08-30 15:55
閱讀 2259·2019-08-30 15:44