volatile
- 線程間的可見性
- 防止指令重排
線程間的可見性
先了解下java的記憶體模型主記憶體和工作記憶體

java的記憶體模型主要目标是定義程式中各個變量的通路規則,即虛拟機中将變量存儲到記憶體和從記憶體中取出變量這樣的底層細節。而這些變量包括執行個體字段、靜态字段和構成數組對象的元素。————摘自深入了解JVM第十二章
如上圖所示,每個線程都有自己獨立的工作記憶體而在這些工作記憶體中使用的變量需要從主記憶體中讀取或存儲到主記憶體中。
好,看下下面這段代碼(——來自Java多線程核心技術)
public class Run {
public static void main(String[] args) {
try {
RunThread runThread = new RunThread();
runThread.start();
Thread.sleep(1000);
runThread.setRunning(false);
System.out.println("已經指派為false");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunThread extends Thread {
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("進入run了");
while (isRunning == true) {
}
System.out.println("退出了!!!!");
}
}
陷入死循環了..... why?
有幾個線程?
2個 一個main,一個thread-0
答案:應該是3個還有個守護線程
//在Thread.sleep(1000);下面加上這兩句
System.out.println(Thread.activeCount());
Thread.currentThread().getThreadGroup().list();
lock(鎖定):作用于主記憶體變量,把一個變量标示為一條線程獨占的狀态
unlock(解鎖):作用于主記憶體的變量,把一個處于鎖定狀态的變量釋放出來,釋放後的變量才可以被其他線程鎖定
read(讀取):作用于主記憶體的變量,把一個變量的值從主記憶體傳輸到線程的工作記憶體中,以便随後的load動作使用
load(載入):作用于工作記憶體的變量,把read操作從主存中得到的變量值放入工作記憶體的變量副本中
use(使用):作用于工作記憶體的變量,把工作記憶體中一個變量的值傳遞給執行引擎,每當虛拟機遇到一個需要使用到變量的值的位元組碼指令時将會執行這個操作
assign(指派):作用于工作記憶體的變量,把一個從執行引擎接收到的值賦給工作記憶體中的變量,每當虛拟機遇到一個給變量指派的位元組碼指令時執行這個操作
store(存儲):作用于工作記憶體的變量,把工作記憶體中一個變量的值傳送到主記憶體中,以便随後的write操作使用
write(寫入):作用于主記憶體的變量,把store操作從工作記憶體中得到的變量的值放入主記憶體的變量中
分析
上圖表示是線程從主記憶體擷取變量到工作記憶體
上圖表示main将isRunning變量修改後更新到主記憶體
又上面兩張圖可知道雖然main線程更新了isRunning變量到主記憶體但是thread-0讀取的程式設計是自己工作記憶體中的資訊isRunning=true,是以會一直陷入循環。
解決方案
使用volatile修飾isRunning變量
private volatile boolean isRunning = true;
使用System.out.println()
while (isRunning == true) {
System.out.println("在循環中!!");
}
why?
下面是println方法的源碼,使用了synchronized塊
/**
* Prints a String and then terminate the line. This method behaves as
* though it invokes <code>{@link #print(String)}</code> and then
* <code>{@link #println()}</code>.
*
* @param x The <code>String</code> to be printed.
*/
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
百度了下再看了下深入了解JVM發現做了鎖優化
鎖粗化
原則上我們在編寫代碼的時候,總是推薦同步塊的作用範圍限制得盡量小————隻在共享資料的實際作域中才進行同步,這樣是為了使用需要同步的操作數量盡可能變小,如果存在鎖競争,那等待鎖的線程也能盡快拿到鎖。
大部分情況下,上面的原則都是正确的,但是如果一系列的連續操作都對同一個對象反複加鎖和解鎖,甚至加鎖操作是出現在循環體中,那即使沒有線程競争,頻繁的進行互斥同步操作也會導緻不必要的性能損耗。如果虛拟機探測到有這樣一串零碎的操作對同一個對象鎖,将會把鎖同步範圍擴充(粗化)到整個操作序列的外部。
--深入了解JVM,13章線程安全與鎖優化
是以應該會變成這樣
synchronized(this){
while(isRunning == true){
}
}
synchronized規定,線程在加鎖時,先清空工作記憶體→在主記憶體中拷貝最新變量的副本到工作記憶體→執行完代碼→将更改後的共享變量的值重新整理到主記憶體中→釋放互斥鎖。
為什麼有這麼多在循環中!!! 應該是Thread.sleep(1000)的原因主線程先休眠1秒再配置isRunning
使用Thread.sleep()
while (isRunning == true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Thread.sleep()是阻塞線程并不釋放鎖,而是讓出cpu排程。 讓出cpu排程後下次執行會重新整理工作記憶體。
是以什麼是可見性?
可見性指當一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。
防止指令重排
指令重排
編譯器為了提高程式的執行效率會按照一定的規則允許指令優化,不影響單線程程式執行結果,但是多線程就會影響程式結果。
參考深入了解JAVA虛拟機
Java多線程程式設計核心技術
https://www.cnblogs.com/LQBlog/p/8718735.html
如果有什麼錯誤歡迎指出來,感謝感謝!才學疏淺望諒解。
轉載于:https://www.cnblogs.com/rookieJW/p/9113273.html