stop方法,no stop 這不是正確的方法,會讓咱們的程序戛然而止,會使咱們不知道哪些工做沒作,完成了什麼任務以及沒有機會去作清理工做。使用的結果會形成程序忽然中止,強行關閉,有時一個循環可能都沒作完。
JAVA中止線程的正確作法—設置退出旗標,使用退出標誌來中止線程,如以前的程序先設置一個布爾類型的值,volatile類型來保證每次都能讀取到它的值,賦值false來退出線程。
JAVA中止線程廣爲流傳的錯誤方法—interrupt方法,interrupt初衷並非中止咱們的線程。
查詢JAVA API文檔,在java.lang包下,找到Thread,Ctrl+F找到interrupt(),找到三個。
interrupt() 中斷線程
interrupted() 測試當前線程是否已經中斷,注意這個方法是靜態方法。
isInterrupted() 測試線程是否已經中斷。後者兩個方法返回的值都是布爾值。
在API中咱們看到:若是線程在調用Object類的wait()、wait(long)或wait(long, int)方法,或者該類的join()、join(long)、join(long, int)、sleep(long)或 sleep(long, int)方法過程當中受阻,則其中斷狀態將被清除,它還將收到一個 InterruptedException。
在這裏首先咱們看到API中interrupt()方法中斷線程是有條件的,在API中提示若是之前的條件都沒有保存,纔會將該線程的中斷狀態設置。此時調用後面的interrupted()或者isInterrupted()將返回一個布爾值的變量true來表示線程被中斷。
若是使用了join方法或者sleep方法使得線程阻塞中斷的狀況下,使用interrupet會使得線程的中斷狀態被清除,而且當前的線程將會收到一個InterruptedException,這表明如後面再調用interrupted或者isInterrupted方法將不會獲得一個正確的值。這就是爲何咱們在使用join方法或者sleep方法須要用try-catch語句包圍來捕獲這個InterruptedException異常的緣由。在使用join或者sleep方法時,一旦其它或當前線程調用了interrupted()方法,它將會收到一個異常。這些被阻塞的線程由於某些緣由須要被喚醒,好比外部發生了中斷,它須要響應,這時它就經過拋出異常的方式來使咱們有機會作出一些響應。因此interrupt並不能正確的中止咱們的線程。java
下面使用代碼來實際演示一下:編程
/** * Created by Administrator on 2017/4/7. */ public class WrongWayStopThread extends Thread { public void run(){ while(!this.isInterrupted()){ //!this.isInterrupted() 一開始使用的是true,線程中止不了,更換後 //正確退出線程,此方法和設置退出標誌法同樣,只是退出旗標比較特殊 //線程是否被中斷的一個狀態 System.out.println("Thread is running..."); //獲取當前系統時間的一個毫秒值 long time=System.currentTimeMillis(); //long time1=System.nanoTime();這個精度到納秒, //比上面currentTimeMillis()毫秒精度高 //while((System.currentTimeMillis()-time)<1000){ //減小咱們的屏幕輸出,大概等於sleep休眠1000毫秒 //也就是每秒中輸出一條信息("Thread is running..." // 這裏爲何沒有使用sleep呢? //} try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //使用sleep方法運行結果,拋出異常,程序沒有正確結束, // sleep方法使線程進入一種阻塞狀態之時,此時若是這個// // 線程再被調用interrupt方法,它會產生兩個結果, // 1是它的中斷狀態被清除,線程的isInterrupted就不能 // 返回一個表示是否被中斷的正確狀態,那麼這裏的while // 就不能正確的退出了。 //2第二個結果就是收到一個InterruptedException異常代表 // 它被中斷了 } } public static void main(String[] args) { WrongWayStopThread thread=new WrongWayStopThread(); //新建一個線程 thread。這裏父類子類引用效果應該是同樣的吧?先照 //示例演示一遍以後再更換成 Thread類型嘗試下 System.out.println("Starting thread..."); //啓動線程 thread.start(); //休眠3秒鐘而且用try-catch語句塊包圍 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Interrupting thread..."); //用 interrupt方法中斷線程,注意前面調用了sleep方法阻塞中斷線程3秒鐘 thread.interrupt(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Stopping application"); } }
程序的任務是創建一個能量盒子的數組,初始值裏數組裏每一個盒子是一個固定的初始值總能量也固定,而後互相傳遞能量。在線程實現後有部分狀況下能量出現丟失。
先創建一個宇宙的能量系統
1.定義了一個私有的數組常量,而且在初始化構造方法中,先用一個參數n表示數組的長度,再利用for循環把數組每一個值都設置爲統一固定的參數值。
2.定義了一個能量傳遞的方法,參數爲(傳遞初的位置下標from,傳遞後的終點位置的下標to,傳遞的能量值),方法體第一句表示若是最初的這個盒子能量不知足本次傳遞則終止本次轉出,if(條件),return;而後是方法體內的公式,數組[from]-=傳遞的能量值,能量轉出後對方要數組[to]+=傳遞的能量值。中間插入輸出語句,用printf格式佔位輸出。注意只是傳遞了一次。
System.out.printf("從%d轉移到%10.2f單位能量到%d",from,amount,to);%d對應咱們參數中的from,%d表明輸出一個整數值,%10.2f表明咱們輸出一個浮點數,而且它的小數部分是兩位,整數點以前的部分應該有十位。System.out.printf("能量總和:%10.2f%n",getTotalEnergies());咱們這裏的%n則相似咱們的轉義字符反斜槓n /n 表明了一個回車換行符。
3.定義一個方法這裏咱們須要獲取這個能量世界的總和。利用for循環遍歷其中的數組元素並設立一個變量初始值爲0,累計相加最後得出並輸出一個總和
4.設置一個方法直接返回當前數組的長度.length數組
/** * Created by Administrator on 2017/4/7. * 宇宙的能量系統 * 遵循能量守恆的定律 * 能量不會憑空消失,只會從一處轉移到另外一處 */ public class EnergySystem { //能量盒子的總和,貯存能量的地方,每一個盒子存儲了必定的能量 //全部的盒子能量之和表明宇宙的總能量 private final double[] energyBoxes; /** * * @param n 表明了能量盒子的數量 * @param initialEnergy 每一個能量盒子初始含有的能量值 */ public EnergySystem(int n,double initialEnergy) { this.energyBoxes = new double[n]; for(int i=0;i<energyBoxes.length;i++){ energyBoxes[i]=initialEnergy; } } //這個構造方法會使這個double數組一開始全部的元素值相等, // 總和=n*initialEnergy,數組構造仍是第一次見 /** *能量的轉移,從一個盒子到另外一個盒子 * @param from 能量源 * @param to 能量終點 * @param amount 轉移的能量值 */ public void transfer(int from,int to,double amount){ //能量轉出的值不足以知足本次能量轉出時,咱們則會終止本次轉出 if(energyBoxes[from]<amount) return; //這個return指的是符合條件則跳出當前方法 System.out.print(Thread.currentThread().getName()); energyBoxes[from]-=amount; System.out.printf("從%d轉移到%10.2f單位能量到%d",from,amount,to); //printf格式輸出,它利用特別的字符來佔位,而後在咱們後面的輸出列表 // 之中,對應其真實的值,例如%d對應咱們參數中的from,%d表明輸出一個 // 整數值,%10.2f表明咱們輸出一個浮點數,而且它的小數部分是兩位, // 整數點以前的部分應該有十位。 energyBoxes[to]+=amount; System.out.printf("能量總和:%10.2f%n",getTotalEnergies()); //咱們這裏的%n則相似咱們的轉義字符反斜槓n /n 表明了一個回車換行符 } /** * 獲取能量世界的能量總和 */ public double getTotalEnergies(){ double sum=0; for (double amount:energyBoxes){ sum+=amount; } return sum; } /** * 返回能量合子的長度 */ public int getBoxAmount(){ return energyBoxes.length; } }
上一個類只是一個宇宙能量系統的類, 提供了能量傳遞的方法和計算總能量和返回數組長度的方法,定義了能量盒子數組。第二個類是咱們的線程任務類,能量傳遞任務,一共設置了四個屬性,第一個是EnergySsytem,由於只使用一個能量世界,定義了一個EnergySystem能量系統的屬性,而且把對象當作構造方法參數傳遞進去。
定義了一個能量轉移的盒子下標 fromBox,這個應該不能超過數組的長度,由於下標從0開始,0-長度-1,還有每次能夠轉移的最大能量值,設置了一個休眠時間,輸入一個帶參構造方法,複寫run方法,能量傳遞的值和toBox,目的盒子的下標,二者都是隨機數。但不能超過最大值,故選擇最大值Math.random()方法,等於或大於0.0,小於1.0之間的隨機數,調取傳遞方法後休眠Math.random()隨機時常,whiletrue循環,注意try塊把try放在循環體外安全
package energysystem; /** * Created by Administrator on 2017/4/7. */ public class EnergyTransferTask implements Runnable{ //共享的能量世界 private EnergySystem energySystem; //能量轉移的源能量盒子下標 private int fromBox; //單詞能量轉移最大單元 private double maxAmount; //最大休眠時間毫秒 private int DELAY=10; public EnergyTransferTask(EnergySystem energySystem,int from,double max){ this.energySystem=energySystem; this.fromBox=from; this.maxAmount=max; } @Override public void run() { try { while(true){ int toBox=(int)(energySystem.getBoxAmount()*Math.random()); //Math.random()是令系統隨機選取大於等於 0.0 且小於 1.0 的僞隨機 double 值 //長度假如說是10*0.9=9,由於不會等於1,因此不管如何也不會超過限度。 double amount=maxAmount*Math.random(); energySystem.transfer(fromBox,toBox,amount); Thread.sleep((int)(DELAY*Math.random())); } }catch (InterruptedException e) { //注意把try塊try的開頭移到循環體以外,在內部的話若是有異常會在循環後來回 // 拋出,容易形成內存溢出,在外面美觀好用 e.printStackTrace(); } } }
運行類多線程
package energysystem; /** * Created by Administrator on 2017/4/7. */ public class EnergySystemTest { //將要構建的能量世界中能量盒子數量 public static final int BOX_AMOUNT=100; //每一個盒子的初始能量 public static final double INITIAL_ENERGY=1000; public static void main(String[] args) { EnergySystem eng=new EnergySystem(BOX_AMOUNT,INITIAL_ENERGY); for(int i=0;i<BOX_AMOUNT;i++){ EnergyTransferTask task=new EnergyTransferTask(eng,i,INITIAL_ENERGY); Thread t=new Thread(task,"TransferThread_"+i); t.start(); } } }
上一節程序最後的結果是在不斷的傳遞能量過程當中,總能量出現減小,而出現這種狀況就是因爲咱們的爭用條件問題形成的。
什麼是爭用條件呢?
利用一個生活化的例子,女神每每受到不少追求者青睞,而女神最後只能選擇一個,當她同時答應多我的的追求時,將難免發生流血事件。同理,把女神比做數據(內存區域)
當多個線程同時共享訪問同一數據(內存區域)時,每一個線程都嘗試操做該數據,從而致使數據被破壞,這種現象咱們就稱之爲爭用條件。
以上面的代碼爲例,線程1和線程2共享了同一個能量轉移目標,同時咱們知道,在同一時間只能有一個線程在cpu上運行,而線程之間的調度則是經過分時和搶度完成的。線程一轉入500,線程二轉入900,因爲爭用條件最後賦值仍是線程1轉入的5500
併發
互斥:相互排斥,在同一時間裏只能有一條線程去對咱們的關鍵數據或者臨界區進行操做,同步:歸根結底,就是線程之間的一種通訊機制。一條線程執行完了某項任務,它會以某種方式告知其它線程,我作完了。在這裏咱們要提到關鍵字 synchronized ,還有對象的.wait 和notifyAll()。新增添一個常量類型是Object對象,做爲咱們的線程鎖app
private final Object lockObj=new Object(); //定義了一個常量,這個是咱們的鎖對象
再去程序中修改一下能量轉移的方法dom
public void transfer(int from,int to,double amount){ //能量轉出的值不足以知足本次能量轉出時,咱們則會終止本次轉出 //synchronized既能夠出現於咱們的方法體之上也能夠出如今咱們的方法體之中 //把這個不知足能量退出的方法加入synchronized修改一下。 //if(energyBoxes[from]<amount) // return; //這個return指的是符合條件則跳出當前方法 synchronized(lockObj){ //這裏的this一開始填的lockObj,經過對對象的加鎖來實現咱們的互斥行爲, //已知加鎖必須是一個共享且惟一的,後面咱們把該對象做爲參數傳遞 //入線程,此時是共享一個對象了 //if(energyBoxes[from]<amount) //return; //上面這個方法表示不知足轉出則程序退出,如今想一想退出以後咱們的這段 // 線程能然有機會去獲取CPU資源,從而再次要求進行加鎖,而咱們的加鎖 // 操做是有開銷的,(以前的話不須要加鎖,大不了多試幾回退出幾回) // 這樣會下降咱們系統的性能。那麼好的辦法是什麼呢? // 當咱們發現條件不知足時,這時咱們應該讓線程去等待某些條件的發生 // 從而下降這個線程去獲取鎖的開銷,提升咱們總體的性能。 //while循環,保證條件不知足時任務都會被條件阻擋,而不是去競爭咱們 // 的CPU資源 while(energyBoxes[from]<amount){ //當咱們發現線程不知足某些條件時,應該將線程阻擋在咱們業務邏輯 // 以前 try { lockObj.wait(); //wait Set等待集合 } catch (InterruptedException e) { e.printStackTrace(); } //這會使咱們的線程進入等待狀態,而避免了咱們的線程去持續的申請鎖 } System.out.print(Thread.currentThread().getName()); energyBoxes[from]-=amount; System.out.printf("從%d轉移到%10.2f單位能量到%d",from,amount,to); //printf格式輸出,它利用特別的字符來佔位,而後在咱們後面的輸出列表 // 之中,對應其真實的值,例如%d對應咱們參數中的from,%d表明輸出一個 // 整數值,%10.2f表明咱們輸出一個浮點數,而且它的小數部分是兩位, // 整數點以前的部分應該有十位。 energyBoxes[to]+=amount; System.out.printf("能量總和:%10.2f%n",getTotalEnergies()); lockObj.notifyAll(); //咱們這裏的%n則相似咱們的轉義字符反斜槓n /n 表明了一個回車換行符 //這裏咱們應該通知被阻擋的線程,告訴被等待的線程,條件發生了變化,他們 //有可能能被執行了 //喚醒全部lockObj對象上等待的線程。 } }
此時再運行咱們的程序,能量就守恆了。ide
什麼是互斥,互斥是怎麼實現的?互斥也就是說關鍵數據在同一時間只能被一個線程所訪問。互斥的實現 synchronized(intrinsic lock) 單詞含義固有的鎖,synchronized(this)至關於給咱們的代碼加一把鎖,使得其餘線程不能進咱們這個關鍵區域去訪問關鍵資源。只有得到了(this)鎖的線程可以進入這個核心區域。java的語法保證了同一時間,只能有一條線程得到咱們的線程鎖。上面的例子就是新建了一個Object對象做爲鎖,感受換成this也徹底能夠實行,由於共享了一個能量世界,鎖住的方法也是這個類的方法。
同步的實現 wait() notify() notifyAll();這三個方法屬於object對象而不是thread對象,object對象的成員函數。如以前的代碼:函數
lockObj.wait(); lockObj.notifyAll();
當能量不知足不能轉移時,調用對象的wait方法讓該線程進入wait set等待的集合,而後繼續下一個線程,當其餘線程執行完畢喚醒全部等待線程。(當有線程執行完,使以前的線程能量獲得知足,擁有足夠的能量能夠知足本次轉移時,繼續執行。)注意這裏的wait和notifyAll不是同一個線程哦。同步是兩個線程之間的交互。wait set,是線程休息室。critical section 咱們的共享資源共享數據又被稱爲臨界區,當有一個線程訪問咱們的資源時,首先它須要得到一把鎖,當它得到了鎖它將進入咱們的臨界區操做,操做過程當中它發現某些狀況不被知足,好比能量不知足轉移。它將調用咱們鎖對象上的wait方法,此時這個線程首先釋放掉咱們的鎖資源,而後進入咱們的鎖對象上的wait set。因爲這個線程釋放掉了咱們的鎖資源,可讓其餘線程來競爭咱們的鎖資源。因此咱們看到其餘線程得到鎖並進入了咱們的臨界區,同時咱們看到waitset中有多條線程在等待條件的知足。當咱們當前線程執行完某些操做,須要通知等待的線程時,調用咱們的notify()方法 將會喚醒咱們鎖資源所持有的等待區域中的1條線程,使這條線程有機會去競爭cpu資源。notifyAll()將喚醒全部等待的線程,去競爭臨界區的鎖對象。
在這裏咱們學習了:
1.如何建立線程及線程的基本操做
2.可見性及volatile關鍵字實現線程的可見性編程
3.爭用條件
4.線程的互斥synchronized
5.線程的同步wait/notifyAll
擴展建議
如何擴展java併發的知識:
1.Java Memory Mode
JMM描述了java線程如何經過內存進行交互,經過這部分知識咱們能夠了解什麼是happens-before原則,爲何要使用happens-before原則,java是如何經過synchronized,volatile&final關鍵字來實現這一原則的。
2.另外能夠看看Lockc&Condition這兩個對象
這兩個是java5.0後引入的是對咱們java鎖機制和等待條件的高層實現,經過它咱們能夠了解咱們如何對程序實現加鎖以及同步的通訊,結合咱們的synchronized和wait以及notifyAll方法,你們能夠更好的理解咱們的互斥與同步是如何實現的。 java.util.concurrent.locks
3.另外一部分咱們能夠了解下線程的安全性
什麼是原子性與可見性的問題,如何經過atomic包來避免咱們原子性編程的問題,java.util.concurrent.atomic,同時當咱們的一個原子操做由多個操做語句構成時,咱們又是如何經過synchronized的方法來實現咱們的原子型操做,相似咱們如何經過synchronized&volatile 來實現咱們的可見性編程,最後你們須要瞭解什麼是死鎖,死鎖產生的條件是什麼,進而能夠書寫出一些避免死鎖發生的線程DeadLocks
4.另一方面多線程編程經常使用的交互模型
Producer-Consumer模型 經典的生產者消費者模型
Read-write Lock模型 讀寫鎖模型
Future模型
Worker Thread模型
在瞭解這些模型的基礎之上,你們能夠考慮在咱們的java併發實現當中,有哪些類是實現了這些模型能夠供咱們直接調用的。
5.最後一方面就是java5引入的一些併發編程工具
java.util.concurrent,都在這個包之下
線程池 ExecutorService
Callable&Future對象
BlockingQueue對象
大大簡化咱們咱們以前的線程編程模型,使咱們能夠更加方便的使用一種面向於任務,更加接近於實際應用需求的一種抽象方式來書寫咱們的線程,使咱們線程有了更大的利用空間。
最後推薦兩本書 core-java 第九版。
java編程的聖經 java concurrency in practice