JUC之volatile關鍵字詳解
阿新 • • 發佈:2019-02-17
一、JUC簡介
- 在Java5.0提供了java.util.concurrent(簡稱JUC包),在此包中增加了在併發程式設計中很常用的工具類,在用於定義類似於執行緒的自定義子系統,包括執行緒池,非同步IO和輕量級任務框架;還提供了設計用於多執行緒上下文中的Collection實現等。
- volatile關鍵字:當多個執行緒進行共享資料時,可以保證記憶體中的資料時可見的;相比較於syschronized是一種較為輕量級的同步策略。
- volatile不具備“互斥性”。
- volatile不能保證變數的“原子性”。
執行結果:package com.itszt; public class Test { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); System.out.println("開啟子執行緒"); myThread.start(); System.out.println("子執行緒開啟完畢"); Thread.sleep(2000); System.out.println("在主執行緒中終止子執行緒"); myThread.setCanRun(false); System.out.println("main裡面的----"+myThread.canRun); } public static class MyThread extends Thread{ private boolean canRun = true; public boolean isCanRun() { return canRun; } public void setCanRun(boolean canRun) { this.canRun = canRun; } @Override public void run() { super.run(); while (canRun) { } System.out.println("子執行緒裡面的:"+canRun); System.out.println("子執行緒終止。。。。"); } } }
上面例子中,我們的main方法的主執行緒與子執行緒MyThread同時對變數canRun變數進行操作,canRun就是多執行緒的共享資料,通過執行結果可以看出,主執行緒更改了canRun的值為false,按理說根據迴圈條件,子執行緒應該會終止,但是結果並沒有終止,而是仍然處在死迴圈中,可見子執行緒中的canRun並沒有被更改,仍然是true。
為什麼會出現這樣的問題呢?我們明明已經更改了變數的值啊?下面,我們來看一下這個變數的值是如何的一個存在,和兩個執行緒是如何取值的。
可以看出,多個執行緒在共享這個資料時,每個執行緒會自己備份這個資料,並單獨進行對資料的操作,這樣,執行緒之間對應這個共享資料時不可見的。
- 解決辦法一:
執行結果:package com.itszt; public class Test { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); System.out.println("開啟子執行緒"); myThread.start(); System.out.println("子執行緒開啟完畢"); Thread.sleep(2000); System.out.println("在主執行緒中終止子執行緒"); myThread.setCanRun(false); System.out.println("main裡面的----"+myThread.canRun); } public static class MyThread extends Thread{ private volatile boolean canRun = true; public boolean isCanRun() { return canRun; } public void setCanRun(boolean canRun) { this.canRun = canRun; } @Override public void run() { super.run(); while (canRun) { } System.out.println("子執行緒裡面的:"+canRun); System.out.println("子執行緒終止。。。。"); } } }
- 解決辦法二:
package com.itszt;
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
System.out.println("開啟子執行緒");
myThread.start();
System.out.println("子執行緒開啟完畢");
Thread.sleep(2000);
System.out.println("在主執行緒中終止子執行緒");
myThread.setCanRun(false);
System.out.println("main裡面的----"+myThread.canRun);
}
public static class MyThread extends Thread{
private boolean canRun = true;
public boolean isCanRun() {
return canRun;
}
public void setCanRun(boolean canRun) {
this.canRun = canRun;
}
@Override
public void run() {
super.run();
while (true){
synchronized (Test.class){
if (!canRun){
break;
}
}
}
System.out.println("子執行緒裡面的:"+canRun);
System.out.println("子執行緒終止。。。。");
}
}
}
執行結果:- i++實際分為三步:讀取--->修改--->寫入
- 原子性:“讀-改-寫”三步為一體的,是不能被拆分的
- 原子變數:JDK5以後,java.util.concurrent.atomic包下,提供了常用的原子變數
- 原子變數的值用volatile修飾,確保變數記憶體可見性
- CAS(Compare-And-Swap)演算法保證資料的原子性
package com.itszt;
/**
*
*
*
* 關鍵字:volatile:保證該資料是被所有子執行緒共享的
*
* 與static的區別:所有的物件共享同一個資料(volatile是作用於多執行緒的,static是作用於多物件的)
*
*
*/
public class Test1 {
private static volatile int i=0;
private static long timeBegin;
public static void main(String[] args){
timeBegin = System.currentTimeMillis();
for (int a = 0; a < 50; a++) {
new MyThread().start();
}
}
private static class MyThread extends Thread{
@Override
public void run() {
super.run();
for (int b = 0; b < 10000; b++) {
i++;
}
System.out.println("最後的值: "+i);
if (i==500000) {
System.out.println("結束的時間為:"+(System.currentTimeMillis()-timeBegin));
}
}
}
}
執行結果:package com.itszt;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
*
* i++ 的原子性 讀-改-寫
*
* 鎖:
*
* 悲觀鎖:
*
* 我們使用的時候別人是不能用的,只有當我們使用完釋放了資源,別人才能用
* synchronized、lock:可以解決原子性問題,但是執行效率都不高
*
* 樂觀鎖:
*
* 多個執行緒可以對同一個資料同時操作,或者同一段程式碼進行操作,只不過操作結果是否生效就不一定了。
*
* JUC是如何幫我們解決這個問題的呢?
*
* JUC基於CAS演算法來做
*
* 多個執行緒對同一個資料進行操作時會留一個版本號,每個執行緒每操作一次,就會更新版本號,將版本號自增1
* 比如:A操作之前的版本號為0,那麼當A操作完之後,A將更改這個版本號為1
* 加入A操作完成之後,在A需要更新版本之前,會檢查當前的版本號,發現版本號發生變化不在是0的時候,
* 或者已經為1,那麼肯定有另外的執行緒,比如B對該資料進行了更改,並且生成了新的版本號,此時A就需要重新執行操作了(讀改寫)
*
*
*
*/
public class Test2 {
//使用原子變數進行改進
private static volatile AtomicInteger i = new AtomicInteger(0);
//宣告一個時間戳
private static long timeBegin;
public static void main(String[] args){
timeBegin = System.currentTimeMillis();
//模擬多執行緒去操作共享資料i
for (int b = 0; b < 50; b++) {
new MyThread().start();
}
}
//定義個執行緒
public static class MyThread extends Thread{
@Override
public void run() {
super.run();
//迴圈執行i++
for (int a = 0; a < 10000; a++) {
i.incrementAndGet();//等同於i++
}
System.out.println("輸出i的值:"+i);
//計算執行完畢的時間
if (i.get()==500000) {
System.out.println("完成時間: "+(System.currentTimeMillis()-timeBegin));
}
}
}
}
執行結果:解決volatile關鍵字存在不能保證原子性的不足,另一種解決方案是使用同步鎖synchronized,但是執行效率遠遠不及樂觀鎖的執行效率
package com.itszt;
/**
*
*
*
* 關鍵字:volatile:保證該資料是被所有子執行緒共享的
*
* 與static的區別:所有的物件共享同一個資料(volatile是作用於多執行緒的,static是作用於多物件的)
*
*
*/
public class Test1 {
private static volatile int i=0;
private static long timeBegin;
public static void main(String[] args){
timeBegin = System.currentTimeMillis();
for (int a = 0; a < 50; a++) {
new MyThread().start();
}
}
private static class MyThread extends Thread{
@Override
public void run() {
super.run();
for (int b = 0; b < 10000; b++) {
//使用同步鎖
synchronized (Test1.class){
i++;
}
}
System.out.println("最後的值: "+i);
if (i==500000) {
System.out.println("結束的時間為:"+(System.currentTimeMillis()-timeBegin));
}
}
}
}
執行結果:四、CAS演算法- CAS(Compare-And-Swap)演算法是硬體對於併發的支援,針對多處理器操作而設計的處理器中的一種特殊指令,用於管理對共享資料的併發訪問;
- CAS是一種無鎖的非阻塞演算法實現
- CAS包含三個運算元:
- 需要讀寫的記憶體值:V
- 進行比較的預估值:A
- 擬寫入的更新值:B
- 當且僅當V==A時,V=B,否則,將重新操作。