盡管線程對象的常用方法可以通過API文檔來了解但是有很多方法僅僅從API說明是無法詳細了解的我們先來說一下線程對象的幾個重要的方法
首先我們來說明start()方法
一個線程對象生成後如果要產生一個執行的線程就一定要調用它的start()方法在介紹這個方法時不得不同時說明run方法其實線程對 象的run方法完全是一個接口回調方法它是你這個線程對象要完成的具體邏輯簡單說你要做什麼就你在run中完成而如何做什麼時候做就不需要你控制 了你只要調用start()方法JVM就會管理這個線程對象讓它產生一個線程並注冊到線程處理系統中
從表面上看start()方法調用了run()方法事實上start()方法並沒有直接調用run方法在JDK以前 start()方法是本地方法它如何最終調用run方法已經不是JAVA程序員所能了解的而在JDK中原來的那個本地start()方法被 start()代替另個一個純JAVA的start()中調用本地方法start()而在start()方法中做了一個驗證就是對一個全局變量 (對象變量)started做檢驗如果為true則start()拋出異常不會調用本地方法start()否則先將該變量設有true然後 調用start()
從中我們可以看到這個為了控制一個線程對象只能運行成功一次start()方法這是因為線程的運行要獲取當前環境包括安全父線程的權限 優先級等條件如果一個線程對象可以運行多次那麼定義一個static 的線程在一個環境中獲取相應權限和優先級運行完成後它在另一個環境中利用原來的權限和優先級等屬性在當前環境中運行這樣就造成無法預知的結果簡單說 來讓一個線程對象只能成功運行一次是基於對線程管理的需要
start()方法最本質的功能是從CPU中申請另一個線程空間來執行 run()方法中的代碼它和當前的線程是兩條線在相對獨立的線程空間運行也就是說如果你直接調用線程對象的run()方法當然也會執行但那是 在當前線程中執行run()方法執行完成後繼續執行下面的代碼而調用start()方法後run()方法的代碼會和當前線程並發(單CPU)或並行 (多CPU)執行
所以請記住一句話[調用線程對象的run方法不會產生一個新的線程]雖然可以達到相同的執行結果但執行過程和執行效率不同
[線程的interrupt()方法interrupted()和isInterrupted()]
這三個方法是關系非常密切而且又比較復雜的雖然它們各自的功能很清楚但它們之間的關系有大多數人不是真正的了解
先說interrupt()方法它是實例方法而它也是最奇怪的方法在java語言中線程最初被設計為隱晦難懂的東西直到現在它的 語義不沒有象它的名字那樣准確大多數人以為一個線程象調用了interrupt()方法那它對應的線程就應該被中斷而拋出異常事實中當一個線程 對象調用interrupt()方法它對應的線程並沒有被中斷只是改變了它的中斷狀態
使當前線程的狀態變以中斷狀態如果沒有其它影響線程還會自己繼續執行
只有當線程執行到sleepwaitjoin等方法時或者自己檢查中斷狀態而拋出異常的情況下線程才會拋出異常
如果線程對象調用interrupt()後它對應的線程就立即中斷那麼interrupted()方法就不可能執行
因為interrupted()方法是一個static方法就是說只能在當前線程上調用而如果一個線程interrupt()後它已經中斷了那它又如何讓自己interrupted()?
正因為一個線程調用interrupt()後只是改變了中斷狀態它可以繼續執行下去在沒有調用sleepwaitjoin等法或自己拋 出異常之前它就可以調用interrupted()來清除中斷狀態(還會原狀)interrupted()方法會檢查當前線程的中斷狀態如果為 被中斷狀態則改變當前線程為非中斷狀態並返回true如果為非中斷狀態則返回false它不僅檢查當前線程是否為中斷狀態而且在保證當 前線程回來非中斷狀態所以它叫interrupted是說中斷的狀態已經結束(到非中斷狀態了)isInterrupted()方法則僅僅檢查線 程對象對應的線程是否是中斷狀態並不改變它的狀態
目前大家只能先記住這三個方法的功能只有真正深入到多線程編程實踐中才會體會到它們為什麼是對象方法為什麼是類方法
線程到底什麼時候才被中斷拋出InterruptedException異常我們將在提高篇中詳細討論
[sleep()
join()
yield()方法]
在現在的環節中
我只能先說明這些方法的作用和調用原則
至於為什麼
在基礎篇中無法深入
只能在提高篇中詳細說明
sleep()方法中是類方法
也就是對當前線程而言的
程序員不能指定某個線程去sleep
只能是當前線程執行到sleep()方法時
睡 眠指定的時間(讓其它線程運行)
事實上也只能是類方法
在當前線程上調用
試想如果你調用一個線程對象的sleep()方法
那麼這個對象對應的線程如 果不是正在運行
它如何sleep()?所以只有當前線程
因為它正在執行
你才能保證它可以調用sleep()方法
原則:[在同步方法中盡量不要調用線程的sleep()方法]
或者簡單說
對於一般水平的程序員你基本不應該調用sleep()方法
join()方法
正如第一節所言
在一個線程對象上調用join方法
是當前線程等待這個線程對象對應的線程結束
比如有兩個工作
工作A要耗時
秒鐘
工作B要耗時
秒或更多
我們在程序中先生成一個線程去做工作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是如何指定的
你怎麼知道是
毫秒而不是
毫秒或是
毫秒?其實這就是OnXXX事件的實質
我們不 是要等多長時間才去做什麼事
而是當等待的工作正好完成的時候去做
yield()方法也是類方法
只在當前線程上調用
理由同上
它主是讓當前線程放棄本次分配到的時間片原則:[不是非常必要的情況下
沒有理 由調用它]
調用這個方法不會提高任何效率
只是降低了CPU的總周期上面介紹的線程一些方法
基於(基礎篇)而言只能簡單提及
以後具體應用中我會結合 實例詳細論述
線程本身的其它方法請參看API文檔
下一節介紹非線程的方法
但和線程密切相關的兩[三]個對象方法:
[wait()
notify()/notifyAll()]
這是在多線程中非常重要的方法
關於這兩個方法
有很多的內容需要說明
在下面的說明中可能會有很多地方不能一下子明白
但在看完本節後
即使不能完全明白
你也一定要回過頭來記住下面的兩句話:
[wait()
notify()/notityAll()方法是普通對象的方法(Object超類中實現)
而不是線程對象的方法]
[wait()
notify()/notityAll()方法只能在同步方法中調用]
[線程的互斥控制]
多個線程同時操作某一對象時
一個線程對該對象的操作可能會改變其狀態
而該狀態會影響另一線程對該對象的真正結果
這個例子我們在太多的文檔中可以看到
就象兩個操售票員同時售出同一張票一樣
線程A 線程B
線程A在數據庫中查詢存票
發現票C可以賣出
class=
left
線程A接受用戶訂票請求
准備出票
這時切換到了線程B執行
線程B在數據庫中查詢存票
發現票C可以賣出
線程B將票賣了出去
切換到線程A執行
線程A賣了一張已經賣出的票
所以需要一種機制來管理這類問題的發生
當某個線程正在執行一個不可分割的部分時
其它線程不能不能同時執行這一部分
象這種控制某一時刻只能有一個線程執行某個執行單元的機制就叫互斥控制或共享互斥(mutual exclusion)
在JAVA中
用synchornized關鍵字來實現互斥控制(暫時這樣認為
JDK
已經發展了新的機制)
[synchornized關鍵字]
把一個單元聲明為synchornized
就可以讓在同一時間只有一個線程操作該方法
有人說synchornized就是一把鎖
事實上它確實存在鎖
但是是誰的鎖
鎖誰
這是一個非常復雜的問題
每個對象只有一把監視鎖(monitor lock)
一次只能被一個線程獲取
當一個線程獲取了這一個鎖後
其它線程就只能等待這個線程釋放鎖才能再獲取
那麼synchornized關鍵字到底鎖什麼?得到了誰的鎖?
對於同步塊
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(o
)部分
但多 個線程同時只有一個可以執行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 要求大於
; if(x <
) wait(); } } 這裡為了說明方法沒有加在try{}catch(){}中
如果沒有明確在哪個對象上調用wait()方法
則為this
wait();
假如:
Test t = new Test();
現在有兩個線程都執行到t
test();方法
其中線程A獲取了t的對象鎖
進入test()方法內
這時x小於
所以線程A進入等待
當一個線程調用了wait方法後
這個線程就進入了這個對象的休息室(waitset)
這是一個虛擬的對象
但JVM中一定存在這樣的一個數據結構用來記錄當前對象中有哪些程線程在等待
當一個線程進入等待時
它就會釋放鎖
讓其它線程來獲取這個鎖
所以線程B有機會獲得了線程A釋放的鎖
進入test()方法
如果這時x還是小於
線程B也進入了t的休息室
這兩個線程只能等待其它線程調用notity[All]來喚醒
但是如果調用的是有參數的wait(time)方法
則線程A
B都會在休息室中等待這個時間後自動喚醒
[為什麼真正的應用都是用while(條件)而不用if(條件)]
在實際的編程中我們看到大量的例子都是用?
while(x <
)
wait();go();而不是用if
為什麼呢?
在多個線程同時執行時
if(x <
)是不安全的
因為如果線程A和線程B都在t的休息室中等待
這時另一個線程使x==
了
並調用notifyAll方法
線程A繼續 執行下面的go()
而它執行完成後
x有可能又小於
比如下面的程序中調用了
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 =
; public void test(){ if(x==
) 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 =
; public synchornized void test(){ if(x==
) try{ wait(); }catch(Exception e){} } public static void main(String[] args) throws Exception{ new MyThread()
start(); } }
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27650.html