通常來講,咱們把正在計算機中執行的程序叫作「進程」(Process) ,而不將其稱爲程序(Program).所謂「線程」(Thread),是「進程」中某個單一順序的控制流.新興的操做系統,如Mac,Windows,Linux,大多采用多線程的概念,把線程視爲基本執行單位.線程也是Java中的至關重要的組成部分之一. java
最簡單的Applet也是由多個線程來完成.在Java中,任何一個Applet的paint()和update()方法都是由AWT(Abstract Window Toolkit)繪圖與事件處理線程調用的,而Applet 主要的方法:init(),start(),stop()和destory() ——是由執行該Applet的應用調用的. 算法
某些地方用輕量進程(Lightweight Process)來代替線程,線程與真正進程的類似性在於它們都是單一順序控制流.然而線程被認爲輕量是因爲它運行於整個程序的上下文內,能使用整個程序共有的資源和程序環境. 瀏覽器
做爲單一順序控制流,在運行的程序內線程必須擁有一些資源做爲必要的開銷.例如,必須有執行堆棧和程序計數器在線程內執行的代碼只在它的上下文中起做用,所以某些地方用「執行上下文」來代替「線程」. 網絡
爲了正確有效地使用線程,必須理解線程的各個方面並瞭解Java 實時系統.必須知道如何提供線程體、線程的生命週期、實時系統如何調度線程、線程組、什麼是幽靈線程(Demon Thread). 多線程
全部的操做都發生在線程體中,在Java中線程體是從Thread類繼承的run()方法,或實現Runnable接口的類中的run()方法.當線程產生並初始化後,實時系統調用它的run()方法.run()方法內的代碼實現所產生線程的行爲,它是線程的主要部分. 併發
詳見線程生命週期. 函數
●新線程態(New Thread) 學習
產生一個Thread對象就生成一個新線程.當線程處於「新線程」狀態時,僅僅是一個空線程對象,它尚未分配到系統資源.所以只能啓動或終止它.任何其餘操做都會引起異常. 測試
●可運行態(Runnable) spa
start()方法產生運行線程所必須的資源,調度線程執行,而且調用線程的run()方法.在這時線程處於可運行態.該狀態不稱爲運行態是由於這時的線程並不老是一直佔用處理機.特別是對於只有一個處理機的PC而言,任什麼時候刻只能有一個處於可運行態的線程佔用處理機.Java經過調度來實現多線程對處理機的共享.
●非運行態(Not Runnable)
當如下事件發生時,線程進入非運行態.
①suspend()方法被調用;
②sleep()方法被調用;
③線程使用wait()來等待條件變量;
④線程處於I/O等待;
●死亡態(Dead)
當run()方法返回,或別的線程調用stop()方法,線程進入死亡態 .一般Applet使用它的stop()方法來終止它產生的全部線程.
雖然咱們說線程是併發運行的.然而事實經常並不是如此.正如前面談到的,當系統中只有一個CPU時,以某種順序在單CPU狀況下執行多線程被稱爲調度(scheduling).Java採用的是一種簡單、固定的調度法,即固定優先級調度.這種算法是根據處於可運行態線程的相對優先級來實行調度.當線程產生時,它繼承原線程的優先級.在須要時可對優先級進行修改.在任什麼時候刻,若是有多條線程等待運行, 系統選擇優先級最高的可運行線程運行.只有當它中止、自動放棄、或因爲某種緣由成爲非運行態低優先級的線程才能運行.若是兩個線程具備相同的優先級,它們將被交替地運行.
Java實時系統的線程調度算法仍是強制性的,在任什麼時候刻,若是一個比其餘線程優先級都高的線程的狀態變爲可運行態,實時系統將選擇該線程來運行.
任何一個Java線程都能成爲幽靈線程.它是做爲運行於同一個進程內的對象和線程的服務提供者.例如,HotJava瀏覽器有一個稱爲「 後臺圖片閱讀器」的幽靈線程,它爲須要圖片的對象和線程從文件系統或網絡讀入圖片.幽靈線程是應用中典型的獨立線程.它爲同一應用中的其餘對象和線程提供服務.幽靈線程的run()方法通常都是無限循環,等待服務請求.
每一個Java線程都是某個線程組的成員.線程組提供一種機制,使得多個線程集於一個對象內,能對它們實行總體操做.譬如,你能用一個方法調用來啓動或掛起組內的全部線程.Java線程組由ThreadGroup類實現.當線程產生時,能夠指定線程組或由實時系統將其放入某個缺省的線程組內.線程只能屬於一個線程組,而且當線程產生後不能改變它所屬的線程組.
在Java中,線程有5中不一樣狀態,分別是:新建(New)、就緒(Runable)、運行(Running)、阻塞(Blocked)和死亡(Dead).它們之間的轉換圖以下:
線程狀態轉換圖
線程生命週期能夠用下圖表示:
一個線程的產生是從咱們調用了start方法開始進入Runnable狀態,便可以被調度運行狀態,並無真正開始運行,調度器能夠將CPU分配給它,使線程進入Running狀態,真正運行其中的程序代碼.線程在運行時幾個可能的去向:
(1)執行時因調度器將CPU分配給了其它線程變爲Runnable狀態,等待被調度.
(2)執行過程沒有遇到任何阻隔,運行完成直接結束,也就是run()方法執行完畢.
(3)執行過程當中請求鎖,進入lock pool中等待對象的鎖,等到會進入Runnable狀態.
(4)執行過程當中遇到wait()方法,被放入wait pool中等待,直到有notify()或interrupt()方法被喚醒或打斷進入lock pool等待對象鎖,等到鎖後進入Runnable狀態.
推薦在run方法中使用控制循環條件的方式來結束一個線程.
wait:告訴當前線程放棄對象鎖並進入等待狀態,直到其餘線程進入同一對象鎖並調用notify爲止.
notify:喚醒同一對象鎖中調用wait的第一個線程.
notifyAll:喚醒同一對象鎖中調用wait的全部線程,具備最高優先級的線程首先被喚醒並執行.
interrupt()方法只是改變中斷狀態而已,它不會中斷一個正在運行的線程.這一方法實際完成的是,給受阻塞的線程發出一箇中斷信號,這樣受阻線程就得以退出阻塞的狀態.更確切的說,若是線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,此時調用該線程的interrupt()方法,那麼該線程將拋出一個InterruptedException中斷異常(該線程必須事先預備好處理此異常),從而提前地終結被阻塞狀態.若是線程沒有被阻塞,這時調用interrupt()將不起做用,直到執行到wait(),sleep(),join()時,才立刻會拋出 InterruptedException.
線程A在執行sleep,wait,join時,線程B調用線程A的interrupt方法,的確這一個時候A會有InterruptedException 異常拋出來.但這實際上是在sleep,wait,join這些方法內部會不斷檢查中斷狀態的值,而本身拋出的InterruptedException.若是線程A正在執行一些指定的操做時如值,for,while,if,調用方法等,不會去檢查中斷狀態,則線程A不會拋出 InterruptedException,而會一直執行着本身的操做.
注意:
①當線程A執行到wait(),sleep(),join()時,拋出InterruptedException後,中斷狀態已經被系統復位了,線程A調用Thread.interrupted()返回的是false.
②若是線程被調用了interrupt(),此時該線程並不在wait(),sleep(),join()時,下次執行wait(),sleep(),join()時,同樣會拋出InterruptedException,固然拋出後該線程的中斷狀態也回被系統復位.
幾種比較:
1. sleep() &interrupt()
線程A正在使用sleep()暫停着: Thread.sleep(100000),若是要取消它的等待狀態,能夠在正在執行的線程裏(好比這裏是B)調用a.interrupt(),令線程A放棄睡眠操做,這裏a是線程A對應到的Thread實例.
當在sleep中時線程被調用interrupt()時,就立刻會放棄暫停的狀態並拋出InterruptedException.拋出異常的,是A線程.
2. wait() &interrupt()
線程A調用了wait()進入了等待狀態,也能夠用interrupt()取消.不過這時候要注意鎖定的問題.線程在進入等待區,會把鎖定解除,當對等待中的線程調用interrupt()時,會先從新獲取鎖定,再拋出異常.在獲取鎖定以前,是沒法拋出異常的.
3. join() &interrupt()
當線程以join()等待其餘線程結束時,當它被調用interrupt(),它與sleep()時同樣,會立刻跳到catch塊裏.
注意:是對誰調用interrupt()方法,必定是調用被阻塞線程的interrupt方法.
使用格式:
class MyThread extends Thread { public void run() { // 定義一個類繼承Thread,覆寫run方法,這裏寫上線程的內容 } public static void main(String[] args) { // 實例調用start方法啓動一個線程 new MyThread().start(); } }
示例:
class ThreadTest extends Thread { private int ticket = 100; public void run() { while (true) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "is saling ticket" + ticket--); } else { break; } } } }main測試類 :
public class ThreadDome1 { public static void main(String[] args) { ThreadTest t = new ThreadTest(); t.start(); t.start(); t.start(); t.start(); } }上面的代碼中 ,咱們用 ThreadTest類模擬售票處的售票過程 ,run方法中的每一次循環都將總票數減 1,模擬賣出一張車票 ,同時該車票號打印出來 ,直接剩餘的票數到零爲止 .在 ThreadDemo1類的 main方法中 ,咱們建立了一個線程對象 ,並重復啓動四次 ,但願經過這種方式產生四個線程 .從運行的結果來看咱們發現其實只有一個線程在運行 ,這個結果告訴咱們: 一個線程對象只能啓動一個線程 ,不管你調用多少遍 start()方法 ,結果只有一個線程 . 運行會報 java.lang.IllegalThreadStateException異常 .
咱們接着修改ThreadDemo1,在main方法中建立四個Thread對象:
示例:
public class ThreadDemo1 { public static void main(String[] args) { new ThreadTest().start(); new ThreadTest().start(); new ThreadTest().start(); new ThreadTest().start(); } }
示例:
class ThreadTest extends Thread { private int ticket = 100; public void run() { while (true) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + " is saling ticket" + ticket--); } else { break; } } } }從結果上看每一個票號都被打印了四次 ,即四個線程各自賣各自的 100張票 ,而不去賣共同的 100張票 .
咱們須要的是,多個線程去處理同一個資源,一個資源只能對應一個對象,在上面的程序中,咱們建立了四個ThreadTest對象,就等於建立了四個資源,每一個資源都有100張票,每一個線程都在獨自處理各自的資源.
總結:要實現這個鐵路售票程序,咱們只能建立一個資源對象,但要建立多個線程去處理同一個資源對象,而且每一個線程上所運行的是相同的程序代碼.
所以能夠考慮使用接口編寫多線程.
使用格式:
class MyThread implements Runnable { public void run() { // 這裏寫上線程的內容 } public static void main(String[] args) { // 使用這個方法啓動一個線程 new Thread(new MyThread()).start(); } //將實現接口的示例做爲Thread實例的參數傳入,調用start()方法. }
示例:
class ThreadTest implements Runnable { private int tickets = 100; public void run() { while (true) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--); } } } }示例 :
public class ThreadDemo1 { public static void main(String[] args) { ThreadTest t = new ThreadTest(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
上面的程序中 ,建立了四個線程 ,每一個線程調用的是同一個 ThreadTest對象中的 run()方法 ,訪問的是同一個對象中的變量( tickets)的實例 ,這個程序知足了咱們的需求 .
java全部實現Runnable接口的類均可被啓動一個新線程,新線程會執行該實例的run()方法,當run()方法執行完畢後,線程就結束了.一旦一個線程執行完畢,這個實例就不能再從新啓動,只能從新生成一個新實例,再啓動一個新線程.
Thread類是實現了Runnable接口的一個實例,它表明一個線程的實例,而且,啓動線程的惟一方法就是經過Thread類的start()實例方法.
start()方法是一個native方法,它將啓動一個新線程,並執行run()方法.Thread類默認的run()方法什麼也不作就退出了.注意:直接調用run()方法並不會啓動一個新線程,它和調用一個普通的java方法沒有什麼區別. 所以,有兩個方法能夠實現本身的線程:
方法1:本身的類extend Thread,並複寫run()方法,就能夠啓動新線程並執行本身定義的run()方法.
方法2:若是本身的類已經extends另外一個類,必須經過實現一個Runnable接口實現.
事實上,當傳入一個Runnable target參數給Thread後,Thread的run()方法就會調用target.run(),參考JDK源代碼:
public void run() { if (target != null) { target.run(); } }實現 Runnable接口相對於繼承 Thread類來講 ,有以下顯著的好處:
①適合多個相同程序代碼的線程去處理同一資源的狀況,把虛擬CPU(線程)同程序的代碼,數據有效的分離,較好地體現了面向對象的設計思想.
②能夠避免因爲Java的單繼承特性帶來的侷限.
③有利於程序的健壯性,代碼可以被多個線程共享,代碼與數據是獨立的.當多個線程的執行代碼來自同一個類的實例時,即稱它們共享相同的代碼.多個線程操做相同的數據,與它們的代碼無關.當共享訪問相同的對象是,即它們共享相同的數據.當線程被構造時,須要的代碼和數據經過一個對象做爲構造函數實參傳遞進去,這個對象就是一個實現了Runnable接口的類的實例.
20150419
JAVA學習筆記系列
--------------------------------------------
聯繫方式
--------------------------------------------
Weibo: ARESXIONG
E-Mail: aresxdy@gmail.com
------------------------------------------------