java多執行緒之volatile關鍵字
阿新 • • 發佈:2019-01-31
在java執行緒併發處理中,關鍵字volatile的主要作用是使變數在多個執行緒間可見。那麼volatile到底該怎麼用了?我們首先來看一段程式碼:
public class MyThread1 implements Runnable { private boolean istag = true; public boolean isIstag() { return istag; } public void setIstag(boolean istag) { this.istag = istag; } public void print() { try { while (istag) { System.out.println("print()執行緒名稱是:"+Thread.currentThread().getName()); Thread.sleep(1000); } } catch (Exception e) { } } @Override public void run() { print(); } }
public class MyThread {
public static void main(String[] args) {
MyThread1 m=new MyThread1();
new Thread(m).start();
System.out.println("我要停止迴圈-->"+Thread.currentThread().getName());
m.setIstag(false);
}
}
此段程式碼如果執行,我們可以發現執行緒能正常停止。如果程式碼執行在-server伺服器模式中64bit的JVM上時,會出現死迴圈。解決的方案是用volatile關鍵字。關鍵字volatile的作用是強制從公共堆疊取得變數的值,而不是從執行緒私有資料棧中取得變數的值。
我們將程式碼改造下:
public class MyThread1 extends Thread { private volatile boolean istag = true; public boolean isIstag() { return istag; } public void setIstag(boolean istag) { this.istag = istag; } @Override public void run() { System.out.println("進入run()===="); while (istag) { System.out.println("執行緒名稱是:"+Thread.currentThread().getName()); } System.out.println("執行緒被停止===="); } }
使用了volatile關鍵字增加了例項變數在多執行緒之間的可見性。但volatile關鍵字最致命的缺點是不支援原子性。
關鍵字synchronized跟volatile進行一下比較:
- 關鍵字volatile是執行緒同步的輕量級實現,所以volatile效能肯定比synchronized要好,並且volatile只能修飾變數,而synchronized可以修飾方法,以及程式碼塊。隨著JDK新版本的釋出,synchronized關鍵字執行效率打打的提高,在開發中使用synchronized還是比較多的。
- 多執行緒訪問volatile不會發生堵塞,而synchronized可能會出現堵塞。
- volatile能保證資料的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有記憶體和公共記憶體中的資料做了同步。
- 關鍵字volatile解決的是變數在多個執行緒之間的可見性;而synchronized關鍵字解決的是多個執行緒之間訪問資源的同步性。
public class MyThread1 extends Thread {
private volatile static int count;
public void addCount(){
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count="+count);
}
@Override
public void run() {
addCount();
}
}
public class RunTest {
public static void main(String[] args){
MyThread1[] myArray=new MyThread1[100];
for (int i = 0; i < 100; i++) {
myArray[i]=new MyThread1();
}
for (int i = 0; i < 100; i++) {
myArray[i].start();
}
}
}
public class MyThread1 extends Thread {
private static int count;
//需要加static,才達到同步效果
public synchronized static void addCount(){
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count="+count);
}
@Override
public void run() {
addCount();
}
}
關鍵字volatile主要使用的場合是在多執行緒中可以感知例項變數被更改了,並且可以獲得最新的值使用,也就是用多執行緒讀取共享變數時可以獲得最新的值。
關鍵字volatile提示執行緒每次從共享記憶體中讀取變數,而不是從私有記憶體中讀取,這樣就保證了同步資料的可見性。但需要注意的是:如果修改例項變數中的資料,比如i++,也就是i=i+1,這樣的操作並不是一個原子操作,也就是非執行緒安全的。表示式i++的實際操作步驟是:
- 從記憶體中讀取i的值;
- 計算i的值;
- 將i的值寫入到記憶體中。
1)對變數的寫操作不依賴於當前值
2)該變數沒有包含在具有其他變數的不變式中
實際上,這些條件表明,可以被寫入 volatile 變數的這些有效值獨立於任何程式的狀態,包括變數的當前狀態。
事實上,我的理解就是上面的2個條件需要保證操作是原子性操作,才能保證使用volatile關鍵字的程式在併發時能夠正確執行。
Java中使用volatile的場景:
package com.ztz.myThread;
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}