auto.js停止所有线程_Java线程与并发编程实践:深入理解volatile和final变量
同步有兩種屬性:互斥性和可見性。synchronized關(guān)鍵字與兩者都有關(guān)系。Java同時也提供了一種更弱的、僅僅包含可見性的同步形式,并且只以volatile關(guān)鍵字關(guān)聯(lián)。
假設(shè)你自己設(shè)計了一個停止線程的機(jī)制(因?yàn)闊o法使用Thread不安全的stop()方法))。清單1中ThreadStopping程序源碼展示了該如何完成這項(xiàng)任務(wù)。
清單1 嘗試停止一個線程
public class ThreadStopping{ public static void main(String[] args) { class StoppableThread extends Thread { private boolean stopped; // defaults to false @Override public void run() { while(!stopped) System.out.println("running"); } void stopThread() { stopped = true; } } StoppableThread thd = new StoppableThread(); thd.start(); try { Thread.sleep(1000); // sleep for 1 second } catch (InterruptedException ie) { } thd.stopThread(); } }清單2中的main()方法聲明了一個叫做StoppableThread的本地類,它繼承自Thread。在初始化完StoppableThread之后,默認(rèn)的主線程啟動和這個 Thread對象關(guān)聯(lián)的線程。之后它睡眠 1 秒,并且在死亡之前調(diào)用StoppableThread的stop()方法。
StoppableThread聲明了一個被初始化為false的stopped實(shí)例變量,stopThread()方法會將該變量設(shè)置為true,同時run()方法中的while循環(huán)會在每次迭代中檢查stopped的值是否已經(jīng)修改為true。
照下面編譯清單2:
javac ThreadStopping.java運(yùn)行程序:
java ThreadStopping 你應(yīng)該能觀測到一系列運(yùn)行時的消息。
當(dāng)你在單處理器/單核的機(jī)器上運(yùn)行這個程序的時候,很可能會觀測到程序停止。但是在一個多處理器的機(jī)器或多核單處理器的機(jī)器上,可能就看不到程序停止,因?yàn)槊總€處理器或者核心很可能有自己的一份stopped的拷貝,當(dāng)一條線程修改了自己的拷貝,其他線程的拷貝并沒有被改變。
你或許決定使用synchronized關(guān)鍵字以確保只能訪問主存中的stopped變量。然后經(jīng)過一番思考,你決定在清單3中使用同步訪問一對臨界區(qū)的方式來解決這個問題。
清單3 嘗試使用synchronized來停止一個線程
public class ThreadStopping{ public static void main(String[] args) { class StoppableThread extends Thread { private boolean stopped; // defaults to false @Override public void run() { synchronized(this) { while(!stopped) System.out.println("running"); } } synchronized void stopThread() { stopped = true; } } StoppableThread thd = new StoppableThread(); thd.start(); try { Thread.sleep(1000); // sleep for 1 second } catch (InterruptedException ie) { } thd.stopThread(); }}出于兩個因素考慮,清單3不是一個好主意。盡管你只需解決可見性的問題,synchronized卻同時解決了互斥的問題(在該程序中不是個問題)。更重要的是,你還往程序中引進(jìn)了另一個更嚴(yán)重的問題。
你已經(jīng)正確地對stopped進(jìn)行了同步訪問,但是進(jìn)一步觀察run()方法中的同步塊,尤其是這個while循環(huán)。由于正在執(zhí)行循環(huán)的這個線程已經(jīng)獲取了當(dāng)前StoppableThread對象(通過synchronized(this))的鎖,這個循環(huán)不會終止。因?yàn)槟J(rèn)的主線程需要獲取相同的鎖,所以它在該對象上調(diào)用stopThread()方法的任意嘗試都會導(dǎo)致自己被阻塞住。
你可以使用局部變量并在同步塊中將stopped的值賦給這個變量來解決這一問題,如下所示:
public void run(){ boolean _stopped = false; while (!_stopped) { synchronized(this) { _stopped = stopped; } System.out.println("running"); }}不過,每次循環(huán)迭代都要嘗試獲取鎖的方式會存在性能開銷(還不如以前),所以這個解決方式是得不償失的。清單4展示了一個更為高效且整潔的方法。
清單4 嘗試通過volatile關(guān)鍵字來停止一個線程
public class ThreadStopping{ public static void main(String[] args) { class StoppableThread extends Thread { private #####volatile boolean stopped; // defaults to false @Override public void run() { while(!stopped) System.out.println("running"); } void stopThread() { stopped = true; } } StoppableThread thd = new StoppableThread(); thd.start(); try { Thread.sleep(1000); // sleep for 1 second } catch (InterruptedException ie) { } thd.stopThread(); } }由于stopped已經(jīng)標(biāo)記為volatile,每條線程都會訪問主存中該變量的拷貝而不會訪問緩存中的拷貝。這樣,即使在多處理器或者多核的機(jī)器上,該程序也會停止。
警告
只有可見性導(dǎo)致問題時,才應(yīng)該使用volatile。而且,你也只能在屬性聲明處才能使用這個保留字(如果你嘗試將局部變量聲明成volatitle,會收到一個錯誤)。最后,你可以將double和long型的屬性聲明成volatile,但是應(yīng)該避免在32位的JVM上這樣做,原因是此時訪問一個double或者long型的變量值需要進(jìn)行兩步操作,若要安全地訪問它們的值,互斥(通過synchronized)是必要的。 當(dāng)一個屬性變量聲明成volatile,就不能同時被聲明final的。不過,由于Java可以讓你安全地訪問final的屬性而無需同步,這也就不能稱之為一個問題了。為了克服DeadlockDemo中的緩存變量問題,我把lock1和lock2都標(biāo)記成final,盡管也能將它們標(biāo)記成volatile的。
以后,你會經(jīng)常使用final關(guān)鍵字來確保在不可變(不會發(fā)生改變)類的上下文中線程的安全性。參考清單5。
清單5 借助于final創(chuàng)建一個不可變且線程安全的類
import java.util.Set;import java.util.TreeSet;public final class Planets{ private final Set planets = new TreeSet<>(); public Planets() { planets.add("Mercury"); planets.add("Venus"); planets.add("Earth"); planets.add("Mars"); planets.add("Jupiter"); planets.add("Saturn"); planets.add("Uranus"); planets.add("Neptune"); } public boolean isPlanet(String planetName) { return planets.contains(planetName); }}清單5展示了一個不可變類Planets,其對象存儲著星球名字的集合。盡管集合是可變的,但這個類的設(shè)計卻保證在構(gòu)造函數(shù)退出之后,集合不會再被改變。通過聲明planets為final,這個屬性的引用不能被更改。而且,該引用也不能被緩存,所以緩存變量的問題也不復(fù)存在。
關(guān)于不可變對象,Java提供了一種特殊的線程安全的保證。即便沒有用同步來發(fā)布(暴露)這些對象的引用,它們依然可以被多條線程安全地訪問。不可變對象提供了下列易于識別的規(guī)則:
- 不可變對象絕對不允許狀態(tài)變更。
- 所有的屬性必須聲明成final。
- 對象必須被恰當(dāng)?shù)貥?gòu)造出來以防this引用脫離構(gòu)造函數(shù)。
最后一點(diǎn)很讓人迷惑,所以這里給出一個this顯式地脫離構(gòu)造函數(shù)的簡單例子:
public class ThisEscapeDemo{ private static ThisEscapeDemo lastCreatedInstance; public ThisEscapeDemo() { lastCreatedInstance = this; }}在www.ibm.com/developerworks/library/j-jtp0618/上查看《Java theory and practice:Safe construction techniques》學(xué)習(xí)更多常見線程風(fēng)險的相關(guān)知識。
本文節(jié)選自《Java線程與并發(fā)編程實(shí)踐》
Java線程和并發(fā)工具是應(yīng)用開發(fā)中的重要部分,備受開發(fā)者的重視,也有一定的學(xué)習(xí)難度。
本書是針對Java 8中的線程特性和并發(fā)工具的快速學(xué)習(xí)和實(shí)踐指南。全書共8章,分別介紹了Thread類和Runnable接口、同步、等待和通知、線程組、定時器框架、并發(fā)工具、同步器、鎖框架,以及高級并發(fā)工具等方面的主題。每章的末尾都以練習(xí)題的方式,幫助讀者鞏固所學(xué)的知識。附錄A給出了所有練習(xí)題的解答,附錄B給出了一個基于Swing線程的教程。
本書適合有一定基礎(chǔ)的Java程序員閱讀學(xué)習(xí),尤其適合想要掌握J(rèn)ava線程和并發(fā)工具的讀者閱讀參考。
總結(jié)
以上是生活随笔為你收集整理的auto.js停止所有线程_Java线程与并发编程实践:深入理解volatile和final变量的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【C语言进阶深度学习记录】二十四 指针
- 下一篇: 如何下载Java API文档?