不同于c++等語言的調用作業系統的線程調控機制來實作多線程,java語言内置了多線程的api包,是以可以更加友善的使用多線程技術。
(1)線程的問題。
程序是程式的一次動态執行過程,它對應了從代碼加載、執行至執行完畢的一個完整過程,這個過程也是程序本身從産生、發展至消亡的過程。線程是比程序更小的機關,一個程序執行過程中可以産生多個線程,每個線程有自身的産生、存在和消亡的過程,也是一個動态的概念。每個程序都有一段專用的記憶體區域,而線程間可以共享相同的記憶體區域(包括代碼和資料),并利用這些共享單元來實作資料交換、實時通信與必要的同步操作。
每個Java程式都有一個預設的主線程。Java程式總是從主類的main方法開始執行。當JVM加載代碼,發現main方法後就啟動一個線程,這個線程就稱作"主線程",該線程負責執行main方法。在main方法中再建立的線程就是其他線程。
如果main方法中沒有建立其他線程,那麼當main方法傳回時JVM就會結束Java應用程式。但如果main方法中建立了其他線程,那麼JVM就要在主線程和其他線程之間輪流切換,保證每個線程都有機會使用CPU資源,main方法傳回(主線程結束)JVM也不會結束,要一直等到該程式所有線程全部結束才結束Java程式(另外一種情況是:程式中調用了Runtime類的exit方法,并且安全管理器允許退出操作發生。這時JVM也會結束該程式)。
(2)JAVA多線程的兩種使用方法
1、繼承Thread類,覆寫run()方法:使用Thread子類建立線程的優點是可以在子類中增加新的成員變量或方法,使線程具有某種屬性或功能。但Java不支援多繼承,Thread類的子類不能再擴充其他的類。
2、實作Runnable接口:用Thread類直接建立線程對象,使用構造函數Thread(Runnable
target)(參數target是一個Runnable接口),建立線程對象時必須向構造方法參數傳遞一個實作Runnable接口類的執行個體,該執行個體對象稱作所創線程的目标對象。當線程調用start()方法,一旦輪到它使用CPU資源,目标對象自動調用接口中的run()方法(接口回調)。
線程間可以共享相同的記憶體單元(包括代碼和資料),并利用這些共享單元來實作資料交換、實時通信與必要的同步操作。對于Thread(Runnable
target)建立的使用同一目标對象的線程,可以共享該目标對象的成員變量和方法。
另外,建立目标對象類在必要時還可以是某個特定類的子類,是以,使用Runnable接口比使用Thread的子類更具有靈活性。
線上程中啟動其他線程,當線程調用start()方法啟動,使之從建立态進入就緒隊列,一旦得到CPU資源就脫離建立它的主線程,開始自己的生命周期。個人覺得,接口技術相比與thread類更加深層次。
(3)多線程中的常用方法
【此處摘自jinguo的ITeye部落格】
start():線程調用該方法将啟動線程,從建立态進入就緒隊列,一旦享用CPU資源就可以脫離建立它的線程,獨立開始自己的生命周期。
run():Thread類的run()方法與Runnable接口中的run()方法功能和作用相同,都用來定義線程對象被排程後所進行的操作,都是系統自動調用而使用者不得引用的方法。run()方法執行完畢,線程就成死亡狀态,即線程釋放了配置設定給它的記憶體(死亡态線程不能再調用start()方法)。線上程沒有結束run()方法前,不能讓線程再調用start()方法,否則将發生IllegalThreadStateException異常。
sleep(int
millsecond):有時,優先級高的線程需要優先級低的線程做一些工作來配合它,此時為讓優先級高的線程讓出CPU資源,使得優先級低的線程有機會運作,可以使用sleep(int
millsecond)方法。線程在休眠時被打斷,JVM就抛出InterruptedException異常。是以,必須在try-catch語句塊中調用sleep方法。
isAlive():當線程調用start()方法并占有CPU資源後該線程的run()方法開始運作,在run()方法沒有結束之前調用isAlive()傳回true,當線程處于建立态或死亡态時調用isAlive()傳回false。
注意:一個已經運作的線程在沒有進入死亡态時,不要再給它配置設定實體,由于線程隻能引用最後配置設定的實體,先前的實體就成為了"垃圾",并且不能被垃圾回收機制收集。
currentThread():是Thread類的類方法,可以用類名調用,傳回目前正在使用CPU資源的線程。
interrupt():當線程調用sleep()方法處于休眠狀态,一個占有CPU資源的線程可以讓休眠的線程調用interrupt()方法"吵醒"自己,即導緻線程發生IllegalThreadStateException異常,進而結束休眠,重新排隊等待CPU資源。
GUI線程:JVM在運作包含圖形界面應用程式時,會自動啟動更多線程,其中有兩個重要的線程:AWT-EventQueue和AWT-Windows。AWT-EventQueue線程負責處理GUI事件,AWT-Windows線程負責将窗體或元件繪制到桌面。
線程同步:(用synchronized修飾某個方法,該方法修改需要同步的變量;或用volatile修飾基本變量)
當兩個或多個線程同時通路一個變量,并且一個線程需要修改這個變量時,應對這樣的問題進行處理,否則可能發生混亂。
要處理線程同步,可以把修改資料的方法用關鍵字synchronized修飾。一個方法使用synchronized修飾,當一個線程A使用這個方法時,其他線程想使用該方法時就必須等待,直到線程A使用完該方法。所謂同步就是多個線程都需要使用一個synchronized修飾的方法。
volatile比同步簡單,隻适合于控制對基本變量(整數、布爾變量等)的單個執行個體的通路。java中的volatile關鍵字與C++中一樣,用volatile修飾的變量在讀寫操作時不會進行優化(取cache裡的值以提高io速度),而是直接對主存進行操作,這表示所有線程在任何時候看到的volatile變量值都相同。
在同步方法中使用wait()、notify()、notifyAll()方法:
當一個線程使用的同步方法中用到某個變量,而此變量又需要其他線程修改後才能符合本線程需要,那麼可以在同步方法中使用wait()方法。中斷方法的執行,使本線程等待,暫時讓出CPU資源,并允許其他線程使用這個同步方法。其他線程如果在使用這個同步方法時不需要等待,那麼它使用完這個同步方法時應當用notifyAll()方法通知所有由于使用這個同步方法而處于等待的線程結束等待。曾中斷的線程就會從中斷處繼續執行,并遵循"先中斷先繼續"的原則。如果用的notify()方法,那麼隻是通知等待中的線程中某一個結束等待。
計時器線程Timer:(Timer還有很多進階操作,詳細見JDK,這裡做個概述)
java.swing.Timer類用于周期性地執行某些操作。有兩個常用構造函數
public
Timer(int delay, ActionListener
listener):參數listener是計時器的螢幕,計時器發生振鈴的事件是ActionEvent類型事件,當振鈴事件發生,螢幕會監視到這個事件并回調ActionListener接口中的actionPerformed(ActionEvent
e)方法。
public Timer(int
delay):使用該構造方法,計時器要再調用addActionListener(ActionListener
listener)方法獲得螢幕。
如果想讓計時器隻震動一次,可以讓計時器調用setRepeats(boolean
b)方法,參數b取false即可。
計時器還可以調用setInitialDelay(int
delay)方法設定首次振鈴的延時,如果沒有設定首次振鈴預設延時為構造函數中的參數delay。
還可以調用getDelay()和setDelay(int
delay)擷取和設定延時。
計時器建立後調用start()啟動,調用stop()停止,即挂起,調用restart()重新啟動計時器,即恢複線程。
線程聯合:
一個線程A在占有CPU資源期間,可以讓其他線程調用join()方法和本線程聯合,如:
B.join();
此時稱A在運作期間聯合了B。這時A線程立刻終端執行,一直等到它聯合的線程B執行完畢,A線程再重新排隊等待CPU資源。但如果A準備聯合的線程B已經結束,則B.join()不會産生任何效果。
守護線程:
線程預設是非守護線程,非守護線程也稱使用者線程,一個線程調用setDaemon(boolean
on)方法可以将自己設定成一個守護(Daemon)線程,如:
thread.setDaemon(true);
一個線程必須在自己運作之前設定自己是否是守護線程。守護線程是當程式中所有使用者線程都已結束運作時即使守護線程的run()方法還有需要執行的語句,守護線程也會立刻結束運作。是以守護線程用于做一些不是很嚴格的工作,當線程随時結束時不會産生什麼不良後果。