1.概念
關於多線程的基礎概念不做過多概述!
線程:簡單來講,就是程序運行的最小單位,比如我們運行的main方法也是一個線程。一個進程可能有幾個線程在運行計算數據。
多線程的優點:多任務操作系統的有點相信都體會過了,可以最大程度上利用CPU 的空閒時間,CPU在多個線程來回切換,大大減少單任務處理由於進程等待帶來的空閒時間的CPU空閒,線程之間是相互獨立的。
2.使用多線程
想要學習一個技術就得接近它 控制它
1) 實現方式
a.繼承Thread
b.實現Runnable接口
先來看看Thread的結構
public class Thread implements Runnable {
從源代碼可以發現Thread實現了Runable接口,他們之間有多態關係其實,使用繼承Thread方式最大的侷限就是多繼承,所以爲了支持多繼承,完全可以實現Runnable接口,一邊繼承一邊實現。兩種方式沒有本質區別,一個是重載一個重寫。
public class Manager extends Thread{ private List<String> queue = new LinkedList<String>(); @Override public void run(){ System.out.println(); } public static void main(String[] args) { Manager m = new Manager(); m.start(); } }
線程執行特性:
1.隨機性
2.不確定性
多線程執行的先後順序是不確定性的,調用start()方法只是告訴"線程規劃器" 我已經做好執行的準備了 具體執行順序讓系統安排一個時間去執行run() 方法。start()的先後順序不決定具體的線程執行順序。具體代碼這裏就不貼了。
2) 線程安全
自定義線程類中的實例變量有共享變量和飛共享變量之分。
a.不共享的情況
各自線程有各自的實例變量相互不影響,多線程操作也能保證安全
b.共享情況
public class Manager extends Thread{ int count = 10; @Override public void run(){ super.run(); count--; System.out.println("由"+Thread.currentThread()+"線程計算count:"+count); } public static void main(String[] args) { Manager m = new Manager(); Thread p = new Thread(m,"Manager1"); Thread c = new Thread(m,"Manager2"); Thread v = new Thread(m,"Manager3"); p.start(); c.start();v.start(); } }
可以發現執行結果:由Thread[Manager2,5,main]線程計算count:8 由Thread[Manager3,5,main]線程計算count:7 由Thread[Manager1,5,main]線程計算count:8
線程1、3對count同時進行了操作,產生非線程安全。想要的結果是依次遞減打印。
i- -可以分解3步:1.去的原有i的值;
2.計算i - -;
3.給i進行賦值;
如果多線程對着個參數進行操作,很有可能在第一步兩個線程同時取到同樣的數據,導致結果重複;其實很典型的列子就是消費者模式,多個消費者做在同一張桌子上吃飯,生產者端吃的。消費者必須等生產者端上來才能開始吃,也就是安排隊的方式。具體怎麼處理後續說明
3) sleep方法
簡單介紹一下sleep()方法於我的理解,當然還有currentThread、isAlive、getId等方法,就不一一介紹了。
a)currentThread() 方法是獲取當前線程對象
b)sleep() 方法是讓當前線程在指定的毫秒數暫停。是指的this.currentThread()返回的線程
這裏就要說到sleep和wait()的區別了:
1.sleep() 是Thread類的一個靜態方法 讓調用的當前線程暫停一段時間 wait 是Object 的方法
2.sleep 不會釋放對象鎖、放棄CPU ,wait會釋放對象鎖 且讓出CPU
3.wait 必須依賴於synchroid ,否者會拋出java.lang.IllegalMonitorStateException 異常,先要獲取對象鎖 可以通過notify 、notifyall 喚醒
4) 停止線程
停止線程意味着在線程處理完任務之前停掉正在做的任務,看似簡單,但必須做好防範,以便達到預期的效果。
停止線程可以用Thread.stop() ,強制性停止當前對象線程,而且會釋放鎖,導致和預期不一致的結果
1) interrrupt()
並不能真正的停止線程:
public class ThreadTest extends Thread{ @Override public void run(){ for(int i = 0; i< 50000;i++){ System.out.println("i:"+i); } } public static void main(String[] args) { ThreadTest test = new ThreadTest(); test.start(); test.interrupt(); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("interrupt"); test.stop(); } }
運行結果:可以看出,調用interrupt() 方法後線程並沒有立即停止,而是設置線程停止標誌.i:49994 i:49995 i:49996 i:49997 i:49998 i:49999
2)判斷線程是否停止
a)Thread. interrupted() 測試當前線程是否停止 靜態方法
b)this.isInterrupted() 判斷線程是否停止 實例方法
那麼這兩個方法有什麼區別呢?先來看看interrupted的解釋:測試當前線程是否中斷
運行結果:public static void main(String[] args) { ThreadTest test = new ThreadTest(); test.start(); Thread.currentThread().interrupt(); System.out.println(Thread.interrupted()); System.out.println(Thread.interrupted()); System.out.println("interrupt"); }
從上述結果來看 ,interrupted()的確判斷出了線程是否中斷狀態,但爲什麼第二個位false呢,從官方幫助文檔查閱發現:true false interrupt i:0
測試當前線程是否中斷,線程的中斷狀態由改方法清除。換句話說調用這個方法後線程的中斷狀態會被清除,這就是爲什麼第二次調用會是false。
再來看isInterrupted()方法:終端正在運行的線程,不是static方法;
運行結果:public class ThreadTest extends Thread{ @Override public void run(){ for(int i = 0; i< 50000;i++){ System.out.println("i:"+i); } } public static void main(String[] args) { ThreadTest test = new ThreadTest(); test.start(); try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Thread.currentThread().interrupt(); test.interrupt(); System.out.println(test.isInterrupted()); System.out.println(test.isInterrupted()); System.out.println("interrupt"); test.stop(); } }
可以看到並沒有清除狀態。i:306 true true interrupt
最後再來看下這兩個方法的解釋吧:
a) interrupted() 測試當前線程中斷狀態,並具有清除標誌置爲false的功能,靜態方法b) isinterrupted() 測試線程的終端狀態,但不清狀態標誌 實例方法
3) 能停止的線程
據以上的內容,可以用在線程中判斷線程狀態的方式來停止線程:
運行結果:public class ThreadTest extends Thread{ @Override public void run(){ for(int i = 0; i< 50000;i++){ if(this.isInterrupted()){ break; } System.out.println("i:"+i); } System.out.println("thread is end"); } public static void main(String[] args) { ThreadTest test = new ThreadTest(); test.start(); try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Thread.currentThread().interrupt(); test.interrupt(); System.out.println(test.isInterrupted()); System.out.println(test.isInterrupted()); } }
i:278 i:279 true true thread is end
從運行結果上看:線程跳出了循環,但是線程並沒有真正的停止。如何解決呢,看下更新後的代碼:
運行結果:public class ThreadTest extends Thread{ @Override public void run(){ try { for(int i = 0; i< 50000;i++){ if(this.isInterrupted()){ throw new InterruptedException(); } System.out.println("i:"+i); } System.out.println("thread is end"); } catch (Exception e) { System.out.println("線程停止,進入catch塊"); // TODO: handle exception } } public static void main(String[] args) { ThreadTest test = new ThreadTest(); test.start(); try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Thread.currentThread().interrupt(); test.interrupt(); System.out.println(test.isInterrupted()); System.out.println(test.isInterrupted()); } }
由此可見線程進入異常停止!i:292 true true 線程停止,進入catch塊
4) 沉睡中停止
如果線程在sleep中停止呢?會發生什麼情況:
運行結果:public class ThreadTest extends Thread{ @Override public void run(){ try { Thread.sleep(200000); System.out.println("thread is end"); } catch (Exception e) { System.out.println("線程停止,進入catch塊"); // TODO: handle exception e.printStackTrace(); } } public static void main(String[] args) { ThreadTest test = new ThreadTest(); test.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } test.interrupt(); System.out.println(test.isInterrupted()); System.out.println(test.isInterrupted()); } }
從以上結果來看,在沉睡中停止線程,會進入到catch(),且清除了線程運行狀態標誌。sleep不會釋放線程鎖及CPU,導致中斷拋出異常false false 線程停止,進入catch塊 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.wyj.design.consumer.ThreadTest.run(ThreadTest.java:7)
相反如果打斷後在沉睡呢:
從以上結果來看,先中斷線程,在沉睡會進入到catch()。i:199998 i:199999 線程停止,進入catch塊 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.wyj.design.consumer.ThreadTest.run(ThreadTest.java:11)
5) 能停止的線程
暴力的停止線程stop().
調用stop() 會拋出ThreadDeth 異常,通常情況下不需要現行的捕捉。
stop 方法已被JDK標記爲過期的方法作廢
1.stop方法會釋放鎖 可能會導致線程安全
2.異常停止線程導致結束流程沒有執行
5) 守護線程
在java線程中有兩種線程:用戶線程、守護線程
守護線程是一種特殊的線程,隨着用戶線程,在JVM中只要有一個非守護線程,守護線程就在工作,最常見的是垃圾回收線程。JVM所有線程結束了,垃圾回收線程也就結束了。
運行結果:public class ThreadTest extends Thread{ @Override public void run(){ try { int i = 0; while(true){ System.out.println("i:"+i++); Thread.sleep(1000); } } catch (Exception e) { System.out.println("線程停止,進入catch塊"); // TODO: handle exception e.printStackTrace(); } } public static void main(String[] args) { ThreadTest test = new ThreadTest(); test.setDaemon(true); test.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
i:0 i:1
用戶線程結束了,守護線程也就結束了。
3 小結
本文介紹了Thread的一些基礎知識。在使用線程的時候可能會發生很多意想不到的情況,這也是多線程一些不可預知的的一個體現,需要學習這個基礎知識和一些常用的情況來規避這些問題。