第十一課 Volatile
例子
public class VolatileTest extends Thread {
/**
* 使用了volatile秒紧,則1秒后,子線程會退出循環(huán)挨下,因為在主線程將isRunning置位為false
*/
//private volatile boolean isRunning = true;
/**
* 不使用volatile熔恢,1秒后,主線程置位isRunning為false臭笆,但主線程對isRunning的修改對子線程不可見叙淌,子線程看見的還是true秤掌,循環(huán)繼續(xù)
*
* 將setRunning方法設(shè)置為synchronized也可以達(dá)到volatile的效果,意思就是同步代碼塊保護的變量修改時也會直接刷新到主存
*
*/
private boolean isRunning = true;
public boolean isRunning(){
return isRunning;
}
public void setRunning(boolean isRunning){
this.isRunning= isRunning;
}
public void run(){
System.out.println("進(jìn)入了run...............");
while (isRunning){}
System.out.println("isUpdated的值被修改為為false,線程將被停止了");
}
public static void main(String[] args) throws InterruptedException {
VolatileTest volatileThread = new VolatileTest();
volatileThread.start();
Thread.sleep(1000);
volatileThread.setRunning(false); //停止線程
}
}
Volatile和原子性沒什么直接關(guān)系
如果變量被同步代碼保護了鹰霍,就不必考慮volatile
怎么引出這個問題呢
public abstract class IntGenerator {
private volatile boolean canceled = false;
public abstract int next();
// Allow this to be canceled:
public void cancel() { canceled = true; }
public boolean isCanceled() { return canceled; }
}
- 分析
- 看這個類的canceled字段机杜,在這里一個IntGenerator對象可以被多個EventChecker對象調(diào)用cancel()
- 這樣就在每個EventChecker的線程里,保留了一份對canceled的本地緩存衅谷,這個本地緩存可能是每個CPU一個
- 在每個線程里調(diào)用修改cenceled的值椒拗,首先會保存到本地緩存,然后也會同步到主存里获黔,據(jù)說這是規(guī)定蚀苛,必須的
- 但是其他線程通過isCanceled()讀取它的值,是從本地緩存讀,沒被改變,即不可見茫负,它已經(jīng)看不見主存里的值了
- 所以用volatile來修飾吓肋,保證每次對它的修改太雨,都會同步到主存的同時,也會對所有其他線程的內(nèi)存可見,或者就是保證對于volatile變量,不會在工作內(nèi)存中拷貝一份雌芽,都是在主存中讀寫
- 整了半天,還是挺麻煩辨嗽,推薦首選用同步來解決問題世落,volatile適用于只有一個字段可變的情況
下面的內(nèi)容來自網(wǎng)頁:http://www.cnblogs.com/MOBIN/p/5407965.html?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
-
摘要
- Volatile是Java提供的一種弱同步機制,當(dāng)一個變量被聲明成volatile類型后編譯器不會將該變量的操作與其他內(nèi)存操作進(jìn)行重排序糟需。
- 在某些場景下使用volatile代替鎖可以減少代碼量和使代碼更易閱讀
-
Volatile的特性
- 可見性:當(dāng)一條線程對volatile變量進(jìn)行了修改操作時屉佳,其他線程能立即知道修改的值,即當(dāng)讀取一個volatile變量時總是返回最近一次寫入的值
- 原子性:對于單個voatile變量其具有原子性(能保證long double類型的變量具有原子性)洲押,但對于i ++ 這類復(fù)合操作其不具有原子性(見下面分析)
-
Volatile使用的前提
- 對變量的寫入操作不依賴變量的當(dāng)前值武花,或者能夠確保只有單一的線程修改變量的值
- 該變量不會與其他狀態(tài)變量一起納入不變性條件中
- 在訪問變量時不需要加鎖
原理:
原因:Java內(nèi)存模型(JMM)規(guī)定了所有的變量都存儲在主內(nèi)存中,主內(nèi)存中的變量為共享變量杈帐,
而每條線程都有自己的工作內(nèi)存体箕,線程的工作內(nèi)存保存了從主內(nèi)存拷貝的變量,
所有對變量的操作都在自己的工作內(nèi)存中進(jìn)行娘荡,完成后再刷新到主內(nèi)存中干旁,
回到例1驶沼,第18行號代碼主線程(線程main)雖然對isRunning的變量進(jìn)行了修改且有刷新
回主內(nèi)存中(《深入理解java虛擬機》中關(guān)于主內(nèi)存與工作內(nèi)存的交互協(xié)議提到變量在工作 內(nèi)存中改變后必須將該變化同步回主內(nèi)存
)炮沐,但volatileThread線程讀的仍是自己工作內(nèi)存
的舊值導(dǎo)致出現(xiàn)多線程的可見性問題,解決辦法就是給isRunning變量加上volatile關(guān)鍵字回怜。
- volatile內(nèi)存語義總結(jié)如下
- 當(dāng)線程對volatile變量進(jìn)行寫操作時大年,會將修改后的值刷新回主內(nèi)存
- 當(dāng)線程對volatile變量進(jìn)行讀操作時换薄,會先將自己工作內(nèi)存中的變量置為無效,之后再通過主內(nèi)存拷貝新值到工作內(nèi)存中使用翔试。
- Synchronized與volatile區(qū)別
- volatile只能修飾變量轻要,而synchronized可以修改變量,方法以及代碼塊
- volatile在多線程中不會存在阻塞問題垦缅,synchronized會存在阻塞問題
- volatile能保證數(shù)據(jù)的可見性冲泥,但不能完全保證數(shù)據(jù)的原子性,synchronized即保證了數(shù)據(jù)的可見性也保證了原子性
- volatile解決的是變量在多個線程之間的可見性壁涎,而sychroized解決的是多個線程之間訪問資源的同步性
第十二課 java提供的并發(fā)構(gòu)件
1 CountDownLatch
package com.cowthan.concurrent.c14;
//: concurrency/CountDownLatchDemo.java
import java.util.concurrent.*;
import java.util.*;
// Performs some portion of a task:
class TaskPortion implements Runnable {
private static int counter = 0;
private final int id = counter++;
private static Random rand = new Random(47);
private final CountDownLatch latch;
TaskPortion(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
doWork();
latch.countDown();
} catch (InterruptedException ex) {
// Acceptable way to exit
}
}
public void doWork() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(rand.nextInt(2000));
System.out.println(this + "completed");
}
public String toString() {
return String.format("%1$-3d ", id);
}
}
// Waits on the CountDownLatch:
class WaitingTask implements Runnable {
private static int counter = 0;
private final int id = counter++;
private final CountDownLatch latch;
WaitingTask(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
latch.await();
System.out.println("Latch barrier passed for " + this);
} catch (InterruptedException ex) {
System.out.println(this + " interrupted");
}
}
public String toString() {
return String.format("WaitingTask %1$-3d ", id);
}
}
public class CountDownLatchDemo {
static final int SIZE = 100;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
// All must share a single CountDownLatch object:
CountDownLatch latch = new CountDownLatch(SIZE);
for (int i = 0; i < 10; i++)
exec.execute(new WaitingTask(latch));
for (int i = 0; i < SIZE; i++)
exec.execute(new TaskPortion(latch));
System.out.println("Launched all tasks");
exec.shutdown(); // Quit when all tasks complete
}
} /* (Execute to see output) */// :~
-
適用于:
- 一組子任務(wù)并行執(zhí)行凡恍,另一組任務(wù)等待著一組完成才進(jìn)行,或等待某個條件完成才進(jìn)行
- 并行執(zhí)行的任務(wù)數(shù)怔球,或者等待的這個條件嚼酝,可以抽象成倒數(shù),倒數(shù)到0竟坛,則另一組任務(wù)就可以繼續(xù)執(zhí)行
- 一個任務(wù)會被分解成多個子任務(wù)x闽巩,y,z
- 其中一個子任務(wù)B會等待其他幾個子任務(wù)完成才會繼續(xù)執(zhí)行
- 所以提供一個CountDownLatch對象担汤,并設(shè)置初始值
- 任務(wù)B在CountDownLatch對象上await:latch.await();
- 每完成一個子任務(wù)涎跨,就在CountDownLatch對象上倒數(shù)一次:latch.countDown();
- 直到倒數(shù)到0,await的對象就會被喚醒
- 任務(wù)B可以有多個
- 一組子任務(wù)并行執(zhí)行凡恍,另一組任務(wù)等待著一組完成才進(jìn)行,或等待某個條件完成才進(jìn)行
-
限制:
- 只能用一次崭歧,如果要用多次六敬,參考CyclicBarrier
2 CyclicBarrier
例子
package com.cowthan.concurrent.c14;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class Horse implements Runnable {
private static int counter = 0;
private final int id = counter++;
private int strides = 0;
private static Random rand = new Random(47);
private static CyclicBarrier barrier;
public Horse(CyclicBarrier b) {
barrier = b;
}
public synchronized int getStrides() {
return strides;
}
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (this) {
strides += rand.nextInt(3); // Produces 0, 1 or 2
}
barrier.await();
}
} catch (InterruptedException e) {
// A legitimate way to exit
} catch (BrokenBarrierException e) {
// This one we want to know about
throw new RuntimeException(e);
}
}
public String toString() {
return "Horse " + id + " ";
}
public String tracks() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < getStrides(); i++)
s.append("*");
s.append(id);
return s.toString();
}
}
class HorseRace {
static final int FINISH_LINE = 75;
private List<Horse> horses = new ArrayList<Horse>();
private ExecutorService exec = Executors.newCachedThreadPool();
private CyclicBarrier barrier;
public HorseRace(int nHorses, final int pause) {
barrier = new CyclicBarrier(nHorses, new Runnable() {
public void run() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < FINISH_LINE; i++)
s.append("="); // The fence on the racetrack
System.out.println(s);
for (Horse horse : horses)
System.out.println(horse.tracks());
for (Horse horse : horses)
if (horse.getStrides() >= FINISH_LINE) {
System.out.println(horse + "won!");
exec.shutdownNow();
return;
}
try {
TimeUnit.MILLISECONDS.sleep(pause);
} catch (InterruptedException e) {
System.out.println("barrier-action sleep interrupted");
}
}
});
for (int i = 0; i < nHorses; i++) {
Horse horse = new Horse(barrier);
horses.add(horse);
exec.execute(horse);
}
}
}
public class CyclicBarrierDemo {
public static void main(String[] args) {
int nHorses = 3; //幾匹馬
int pause = 200; //等多久走一步
new HorseRace(nHorses, pause);
}
}
-
適用于:
- 某個人物要等待多個任務(wù)并行進(jìn)行,直到都完成驾荣,才會執(zhí)行
- 可以重用
- 不得不說外构,CyclicBarrier還有點不好理解,看了demo代碼還是沒整明白
- 怎么是horse在barrier上await呢
- CyclicBarrier構(gòu)造怎么還得傳入必須await的線程個數(shù)呢
-
介紹
- 構(gòu)造:barrier = new CyclicBarrier(n, new Runnable(){})
- 參數(shù)1:計數(shù)值播掷,當(dāng)有線程在barrier上await時审编,計數(shù)減一,n個線程都await了歧匈,計數(shù)就成0了垒酬,柵欄動作就會執(zhí)行
- 參數(shù)2:叫做柵欄動作,計數(shù)到0時件炉,會自動執(zhí)行
- 構(gòu)造:barrier = new CyclicBarrier(n, new Runnable(){})
-
CyclicBarrierDemo講解:
- 柵欄動作做的事情是:
- 打印線路勘究,打印終點
- 打印每匹馬當(dāng)前的位置
- 判斷是否有馬走到終點,有則提示奪冠斟冕,并結(jié)束所有線程(shutdownNow)
- 柵欄動作執(zhí)行完后口糕,計數(shù)又會重置,此時
- 每匹馬再向前走一步磕蛇,距離是隨機數(shù)
- 走完之后景描,await一下
- 所有馬都走完一步十办,await倒數(shù)計數(shù)值又是0了,再激活柵欄動作
- 如此循環(huán)
- 所以超棺,柵欄動作等所有子任務(wù)都await了向族,才運行,此時所有子任務(wù)都阻塞棠绘,子任務(wù)等柵欄動作完成件相,計數(shù)自動重置,再被喚醒
- 柵欄動作做的事情是:
-
總結(jié):
- 構(gòu)造時氧苍,傳入計數(shù)值和柵欄動作
- 計數(shù)值減一操作由子任務(wù)的await完成
- 柵欄動作在計數(shù)值為0時激活适肠,并且運行完會自動重置計數(shù)值,并喚醒await的線程們
- 更多:
- 思考:如果沒有CyclicBarrier候引,仿真賽馬你會怎么實現(xiàn)侯养?
- 你的實現(xiàn)會考慮起始和終結(jié)的情況嗎?宣布奪冠之后澄干,所有的馬都能立即停止前進(jìn)嗎逛揩?統(tǒng)計開始和結(jié)束時,所有馬的狀態(tài)保持前后一致嗎麸俘?
- 提示1:CyclicBarrier的子任務(wù)辩稽,會在await上等待柵欄動作的結(jié)束,并且await是可以被interrupt的
- 提示2:賽場統(tǒng)計是由柵欄動作完成的从媚,此動作會在每一批馬都前進(jìn)一步之后逞泄,所有馬都await,柵欄動作開始統(tǒng)計