標籤:
這裡我們做一個完整的範例來說明線程產生的方式不同而產生的線程的差別:
package debug;
import java.io.*;
import java.lang.Thread;
class MyThread extends Thread{
public int x = 0;
public void run(){
System.out.println(++x);
}
}
class R implements Runnable{
private int x = 0;
public void run(){
System.out.println(++x);
}
}
public class Test {
public static void main(String[] args) throws Exception{
for(int i=0;i<10;i++){
Thread t = new MyThread();
t.start();
}
Thread.sleep(10000);//讓上面的線程執行完畢
R r = new R();
for(int i=0;i<10;i++){
Thread t = new Thread(r);
t.start();
}
}
}
上面10個線程對象產生的10個線程執行時列印了10次1。以下10個線程對象產生的10個線程執行時列印了1到10。我們把以下的10個線程稱為同一執行個體(Runnable執行個體)的多個線程。
下節我們將研究線程對象方法,還是那句話,一般文檔中能夠讀到的內容我不會介紹太多
請大家自己瞭解。
線程對象的幾個重要的方法
雖然線程對象的經常用法能夠通過API文檔來瞭解,可是有非常多方法只從API說明是無法具體瞭解的。
本來打算用一節的篇幅來把線程方法中一些重要的知識說完,但這樣下來預計要非經常的篇幅,可能要用好幾節才幹說把和線程方法相關的一些重要的知識說完。
首先我們接基礎篇(二)來說明start()方法。
一個線程對象產生後,假設要產生一個啟動並執行線程,就一定要調用它的start()方法.在介紹這種方法時不得不同一時候說明run方法.事實上線程對 象的run方法全然是一個介面回調方法,它是你這個線程對象要完畢的詳細邏輯.簡單說你要做什麼就你在run中完畢,而怎樣做,什麼時候做就不須要你控制 了,你僅僅要調用start()方法,JVM就會管理這個線程對象讓它產生一個線程並注冊到線程處理系統中。
從表面上看,start()方法調用了run()方法,其實,start()方法並沒有直接調用run方法.在JDK1.5曾經 start()方法是本地方法,它怎樣終於調用run方法已經不是JAVA程式猿所能瞭解的.而在JDK1.5中,原來的那個本地start()方法被 start0()取代,另個一個純JAVA的start()中調用本地方法start0(),而在start()方法中做了一個驗證,就是對一個全域變數 (物件變數)started做檢驗,假設為true,則start()拋出異常,不會調用本地方法start0(),否則,先將該變數設有true,然後 調用start0()。
從中我們能夠看到這個為了控制一個線程對象僅僅能執行成功一次start()方法.這是由於線程的執行要擷取當前環境,包含安全,父線程的許可權, 優先順序等條件,假設一個線程對象能夠執行多次,那麼定義一個static 的線程在一個環境中擷取對應許可權和優先順序,執行完畢後它在還有一個環境中利用原來的許可權和優先順序等屬性在當前環境中執行,這樣就造成無法預知的結果.簡單說 來,讓一個線程對象僅僅能成功執行一次,是基於對線程管理的須要。
start()方法最本質的功能是從CPU中申請還有一個線程空間來執行 run()方法中的代碼,它和當前的線程是兩條線,在相對獨立的線程空間執行,也就是說,假設你直接調用線程對象的run()方法,當然也會執行,但那是 在當前線程中執行,run()方法執行完畢後繼續執行以下的代碼.而調用start()方法後,run()方法的代碼會和當前線程並發(單CPU)或並行 (多CPU)執行。
所以請記住一句話[調用線程對象的run方法不會產生一個新的線程],儘管能夠達到同樣的運行結果,但運行過程和運行效率不同。
[線程的interrupt()方法,interrupted()和isInterrupted()]
這三個方法是關係非常密切並且又比較複雜的,儘管它們各自的功能非常清楚,但它們之間的關係有大多數人不是真正的瞭解。
先說interrupt()方法,它是執行個體方法,而它也是最奇怪的方法,在java語言中,線程最初被設計為"隱晦難懂"的東西,直到如今它的 語義不沒有象它的名字那樣準確。大多數人以為,一個線程象調用了interrupt()方法,那它相應的線程就應該被中斷而拋出異常,事實中,當一個線程 對象調用interrupt()方法,它相應的線程並沒有被中斷,僅僅是改變了它的中斷狀態。
使當前線程的狀態變以中斷狀態,假設沒有其他影響,線程還會自己繼續運行。
僅僅有當線程運行到sleep,wait,join等方法時,或者自己檢查中斷狀態而拋出異常的情況下,線程才會拋出異常。
假設線程對象調用interrupt()後它相應的線程就馬上中斷,那麼interrupted()方法就不可能運行。
由於interrupted()方法是一個static方法,就是說僅僅能在當前線程上調用,而假設一個線程interrupt()後它已經中斷了,那它又怎樣讓自己interrupted()?
正由於一個線程調用interrupt()後不過改變了中斷狀態,它能夠繼續運行下去,在沒有調用sleep,wait,join等法或自己拋 出異常之前,它就能夠調用interrupted()來清除中斷狀態(還會原狀)interrupted()方法會檢查當前線程的中斷狀態,假設為 "被中斷狀態"則改變當前線程為"非中斷狀態"並返回true,假設為"非中斷狀態"則返回false,它不僅檢查當前線程是否為中斷狀態,並且在保證當 前線程回來非中斷狀態,所以它叫"interrupted",是說中斷的狀態已經結束(到非中斷狀態了)isInterrupted()方法則只檢查線 程對象相應的線程是否是中斷狀態,並不改變它的狀態。
眼下大家僅僅能先記住這三個方法的功能,僅僅有真正深入到多線程編程實踐中,才會體會到它們為什麼是對象方法,為什麼是類方法。
線程究竟什麼時候才被中斷拋出InterruptedException異常,我們將在提高篇中具體討論。
[sleep(),join(),yield()方法]
在如今的環節中,我僅僅能先說明這些方法的作用和調用原則,至於為什麼,在基礎篇中無法深入,僅僅能在提高篇中具體說明。
sleep()方法中是類方法,也就是對當前線程而言的,程式猿不能指定某個線程去sleep,僅僅能是當前線程執行到sleep()方法時,睡 眠指定的時間(讓其他線程執行).其實也僅僅能是類方法,在當前線程上調用.試想假設你調用一個線程對象的sleep()方法,那麼這個對象相應的線程如 果不是正在執行,它怎樣sleep()?所以僅僅有當前線程,由於它正在執行,你才幹保證它能夠調用sleep()方法。
原則:[在同步方法中盡量不要調用線程的sleep()方法],或者簡單說,對於一般水平的程式猿你基本不應該調用sleep()方法。
join()方法,正如第一節所言,在一個線程對象上調用join方法,是當前線程等待這個線程對象相應的線程結束,比方有兩個工作,工作A要耗時10秒鐘,工作B要耗時10秒或很多其它。我們在程式中先產生一個線程去做工作B,然後做工作A。
new?B().start();//做工作B
A();//做工作A
工作A完畢後,以下要等待工作B的結果來進行處理.假設工作B還沒有完畢我就不能進行以下的工作C,所以
B?b?=?new?B();
b.start();//做工作B
A();//做工作A
b.join();//等工作B完畢。
C();//繼續工作C。
原則:[join是測試其他工作狀態的唯一正確方法],我見過非常多人,甚至有的是博士生,在處理一項工作時假設還有一項工作沒有完畢,說讓當前工 作線程sleep(x),我問他,你這個x是怎樣指定的,你怎麼知道是100毫秒而不是99毫秒或是101毫秒?事實上這就是OnXXX事件的實質,我們不 是要等多長時間才去做什麼事,而是當等待的工作正好完畢的時候去做。
yield()方法也是類方法,僅僅在當前線程上調用,理由同上,它主是讓當前線程放棄本次分配到的時間片原則:[不是很必要的情況下,沒有理 由調用它].調用這種方法不會提高不論什麼效率,僅僅是減少了CPU的總周期上面介紹的線程一些方法,基於(基礎篇)而言僅僅能簡單提及.以後具體應用中我會結合 執行個體具體論述。
線程本身的其他方法請參看API文檔.下一節介紹非線程的方法,但和線程密切相關的兩[三]個對象方法:
[wait(),notify()/notifyAll()]
這是在多線程中很重要的方法。
關於這兩個方法,有非常多的內容須要說明.在以下的說明中可能會有非常多地方不能一下子明確,但在看完本節後,即使不能全然明確,你也一定要回過頭來記住以下的兩句話:
[wait(),notify()/notityAll()方法是普通對象的方法(Object超類中實現),而不是線程對象的方法]
[wait(),notify()/notityAll()方法僅僅能在同步方法中調用]
[線程的相互排斥控制]
多個線程同一時候操作某一對象時,一個線程對該對象的操作可能會改變其狀態,而該狀態會影響還有一線程對該對象的真正結果.
這個範例我們在太多的文檔中能夠看到,就象兩個操售票員同一時候售出同一張票一樣.
線程A |
線程B |
1.線程A在資料庫中查詢存票,發現票C能夠賣出 |
|
class="left"2.線程A接受使用者訂票請求,準備出票. |
|
|
3.這時切換到了線程B運行 |
|
4.線程B在資料庫中查詢存票,發現票C能夠賣出 |
|
5.線程B將票賣了出去 |
6.切換到線程A運行,線程A賣了一張已經賣出的票 |
|
所以須要一種機制來管理這類問題的發生,當某個線程正在運行一個不可切割的部分時,其他線程不能不能同一時候運行這一部分.
象這樣的控制某一時刻僅僅能有一個線程運行某個運行單元的機制就叫相互排斥控制或共用相互排斥(mutual exclusion)
在JAVA中,用synchornizedkeyword來實現相互排斥控制(臨時這樣覺得,JDK1.5已經發展了新的機制)
[synchornizedkeyword]
把一個單元聲明為synchornized,就能夠讓在同一時間僅僅有一個線程操作該方法.
有人說synchornized就是一把鎖,其實它確實存在鎖,可是是誰的鎖,鎖誰,這是一個很複雜的問題.
每一個對象僅僅有一把監視鎖(monitor lock),一次僅僅能被一個線程擷取.當一個線程擷取了這一個鎖後,其他線程就僅僅能等待這個線程釋放鎖才幹再擷取.
那麼synchornizedkeyword究竟鎖什麼?得到了誰的鎖?
對於同步塊,synchornized擷取的是參數中的對象鎖:
synchornized(obj){
//...............
}
線程運行到這裡時,首先要擷取obj這個執行個體的鎖,假設沒有擷取到線程僅僅能等待.假設多個線程運行到這裡,僅僅能有一個線程擷取obj的鎖,然後運行{}中的語句,所以,obj對象的作用範圍不同,控製程序不同.
假如:
public void test(){
Object o = new Object();
synchornized(obj){
//...............
}
}
這段程式控制不了不論什麼,多個線程之間運行到Object o = new Object();時會各自產生一個對象然後擷取這個對象有監視鎖,各自皆大歡喜地運行.
而假設是類的屬性:
class Test{
Object o = new Object();
public void test(){
synchornized(o){
//...............
}
}
}
全部運行到Test執行個體的synchornized(o)的線程,僅僅有一個線程能夠擷取到監視鎖.
有時我們會這樣:
public void test(){
synchornized(this){
//...............
}
}
那麼全部運行Test執行個體的線程僅僅能有一個線程運行.而synchornized(o)和synchornized(this)的範圍是不同 的,由於運行到Test執行個體的synchornized(o)的線程等待時,其他線程能夠運行Test執行個體的synchornized(o1)部分,但多 個線程同一時候僅僅有一個能夠運行Test執行個體的synchornized(this).]
而對於
synchornized(Test.class){
//...............
}
這種同步塊而言,全部調用Test多個執行個體的線程賜教僅僅能有一個線程能夠運行.
[synchornized方法]
假設一個方法聲明為synchornized的,則等同於把在為個方法上調用synchornized(this).
假設一個靜態方法被聲明為synchornized,則等同於把在為個方法上調用synchornized(類.class).
如今進入wait方法和notify/notifyAll方法.這兩個(或叫三個)方法都是Object對象的方法,而不是線程對象的方法.如同鎖一樣,它們是線上程中調用某一對象上啟動並執行.
class Test{
public synchornized void test(){
//擷取條件,int x 要求大於100;
if(x < 100)
wait();
}
}
這裡為了說明方法沒有加在try{}catch(){}中,假設沒有明白在哪個對象上調用wait()方法,則為this.wait();
假如:
Test t = new Test();
如今有兩個線程都運行到t.test();方法.當中線程A擷取了t的對象鎖,進入test()方法內.
這時x小於100,所以線程A進入等待.
當一個線程調用了wait方法後,這個線程就進入了這個對象的歇息室(waitset),這是一個虛擬對象,但JVM中一定存在這種一個資料結構用來記錄當前對象中有哪些程線程在等待.
當一個線程進入等待時,它就會釋放鎖,讓其他線程來擷取這個鎖.
所以線程B有機會獲得了線程A釋放的鎖,進入test()方法,假設這時x還是小於100,線程B也進入了t的歇息室.
這兩個線程僅僅能等待其他線程調用notity[All]來喚醒.
可是假設調用的是有參數的wait(time)方法,則線程A,B都會在歇息室中等待這個時間後自己主動喚醒.
[為什麼真正的應用都是用while(條件)而不用if(條件)]
在實際的編程中我們看到大量的範例都是用?
while(x < 100)
wait();go();而不是用if,為什麼呢?
在多個線程同一時候運行時,if(x <100)是不安全的.由於假設線程A和線程B都在t的歇息室中等待,這時還有一個線程使x==100了,並調用notifyAll方法,線程A繼續 運行以下的go().而它運行完畢後,x有可能又小於100,比方以下的程式中調用了--x,這時切換到線程B,線程B沒有繼續推斷,直接運行go(); 就產生一個錯誤的條件,僅僅有while才幹保證線程B又繼續檢查一次.
[notify/notifyAll方法]
這兩個方法都是把某個對象上歇息區內的線程喚醒,notify僅僅能喚醒一個,但到底是哪一個不能確定,而notifyAll則喚醒這個對象上的歇息室中全部的線程.
一般有為了安全性,我們在絕對多數時候應該使用notifiAll(),除非你明白知道僅僅喚醒當中的一個線程.
那麼是否是僅僅要調用一個對象的wait()方法,當前線程就進入了這個對象的歇息室呢?事實中,要調用一個對象的wait()方法,僅僅有當前線程擷取了這個對象的鎖,換句話說一定要在這個對象的同步方法或以這個對象為參數的同步塊中.
class MyThread extends Thread{
Test t = new Test();
public void run(){
t.test();
System.out.println("Thread say:Hello,World!");
}
}
public class Test {
int x = 0;
public void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
這個線程就不會進入t的wait方法而直接列印出Thread say:Hello,World!.
而假設改成:
public class Test {
int x = 0;
public synchornized void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
我們就能夠看到線程一直等待,注意這個線程進入等待後沒有其他線程喚醒,除非強行退出JVM環境,否則它一直等待.
所以請記住:
[線程要想調用一個對象的wait()方法就要先獲得該對象的監視鎖,而一旦調用wait()後又馬上釋放該鎖]
轉載地址:http://blog.zol.com.cn/860/article_859847.html
java多線程範例