小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

08語法總結(jié) - 線程

 靜聽沙漏 2012-02-14
Java語法總結(jié) - 線程

一提到線程好像是件很麻煩很復(fù)雜的事,事實上確實如此,涉及到線程的編程是很講究技巧的。這就需要我們變換思維方式,了解線程機(jī)制的比較通用的技巧,寫出高效的、不依賴于某個JVM實現(xiàn)的程序來。畢竟僅僅就Java而言,各個虛擬機(jī)的實現(xiàn)是不同的。學(xué)習(xí)線程時,最令我印象深刻的就是那種不確定性、沒有保障性,各個線程的運行完全是以不可預(yù)料的方式和速度推進(jìn),有的一個程序運行了N次,其結(jié)果差異性很大。


1、什么是線程?線程是彼此互相獨立的、能獨立運行的子任務(wù),并且每個線程都有自己的調(diào)用棧。所謂的多任務(wù)是通過周期性地將CPU時間片切換到不同的子任務(wù),雖然從微觀上看來,單核的CPU上同時只運行一個子任務(wù),但是從宏觀來看,每個子任務(wù)似乎是同時連續(xù)運行的。(但是JAVA的線程不是按時間片分配的,在本文的最后引用了一段網(wǎng)友翻譯的JAVA原著中對線程的理解。)

2、在java中,線程指兩個不同的內(nèi)容:一是java.lang.Thread類的一個對象;另外也可以指線程的執(zhí)行。線程對象和其他的對象一樣,在堆上創(chuàng)建、運行、死亡。但不同之處是線程的執(zhí)行是一個輕量級的進(jìn)程,有它自己的調(diào)用棧。
可以這樣想,每個調(diào)用棧都對應(yīng)一個線程,每個線程又對應(yīng)一個調(diào)用棧。
我們運行java程序時有一個入口函數(shù)main()函數(shù),它對應(yīng)的線程被稱為主線程。一個新線程一旦被創(chuàng)建,就產(chǎn)生一個新調(diào)用棧,從原主線程中脫離,也就是與主線程并發(fā)執(zhí)行。


4、當(dāng)提到線程時,很少是有保障的。我們必須了解到什么是有保障的操作,什么是無保障的操作,以便設(shè)計的程序在各種jvm上都能很好地工作。比如,在某些jvm實現(xiàn)中,把java線程映射為本地操作系統(tǒng)的線程。這是java核心的一部分。

5、線程的創(chuàng)建。
創(chuàng)建線程有兩種方式:
A、繼承java.lang.Thread類。
class ThreadTest extends Thread{
public void run() {
System.out.println ("someting run here!");
}
public void run(String s){
System.out.println ("string in run is " + s);
}
public static void main (String[] args) {
ThreadTest tt = new ThreadTest();
tt.start();
tt.run("it won't auto run!");
}
}

輸出的結(jié)果比較有趣:
string in run is it won't auto run!
someting run here!
注意輸出的順序:好像與我們想象的順序相反了!為什么呢?
一旦調(diào)用start()方法,必須給JVM點時間,讓它配置進(jìn)程。而在它配置完成之前,重載的run(String s)方法被調(diào)用了,結(jié)果反而先輸出了“string in run is it won't auto run!”,這時tt線程完成了配置,輸出了“someting run here!”。
這個結(jié)論是比較容易驗證的:
修改上面的程序,在tt.start();后面加上語句for (int i = 0; i<10000; i++); 這樣主線程開始執(zhí)行運算量比較大的for循環(huán)了,只有執(zhí)行完for循環(huán)才能運行后面的tt.run("it won't auto run!");語句。此時,tt線程和主線程并行執(zhí)行了,已經(jīng)有足夠的時間完成線程的配置!因此先到一步!修改后的程序運行結(jié)果如下:
someting run here!
string in run is it won't auto run!
注意:這種輸出結(jié)果的順序是沒有保障的!不要依賴這種結(jié)論!

沒有參數(shù)的run()方法是自動被調(diào)用的,而帶參數(shù)的run()是被重載的,必須顯式調(diào)用。
這種方式的限制是:這種方式很簡單,但不是個好的方案。如果繼承了Thread類,那么就不能繼承其他的類了,java是單繼承結(jié)構(gòu)的,應(yīng)該把繼承的機(jī)會留給別的類。除非因為你有線程特有的更多的操作。
Thread類中有許多管理線程的方法,包括創(chuàng)建、啟動和暫停它們。所有的操作都是從run()方法開始,并且在run()方法內(nèi)編寫需要在獨立線程內(nèi)執(zhí)行的代碼。run()方法可以調(diào)用其他方法,但是執(zhí)行的線程總是通過調(diào)用run()。

B、實現(xiàn)java.lang.Runnable接口。
class ThreadTest implements Runnable {
public void run() {
System.out.println ("someting run here");
}
public static void main (String[] args) {
ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();
t2.start();
//new Thread(tt).start();
}
}

比第一種方法復(fù)雜一點,為了使代碼被獨立的線程運行,還需要一個Thread對象。這樣就把線程相關(guān)的代碼和線程要執(zhí)行的代碼分離開來。

另一種方式是:參數(shù)形式的匿名內(nèi)部類創(chuàng)建方式,也是比較常見的。
class ThreadTest{
public static void main (String[] args) {
Thread t = new Thread(new Runnable(){
public void run(){
System.out.println ("anonymous thread");
}
});

t.start();
}
}
如果你對此方式的聲明不感冒,請參看本人總結(jié)的內(nèi)部類。

第一種方式使用無參構(gòu)造函數(shù)創(chuàng)建線程,則當(dāng)線程開始工作時,它將調(diào)用自己的run()方法。
第二種方式使用帶參數(shù)的構(gòu)造函數(shù)創(chuàng)建線程,因為你要告訴這個新線程使用你的run()方法,而不是它自己的。
如上例,可以把一個目標(biāo)賦給多個線程,這意味著幾個執(zhí)行線程將運行完全相同的作業(yè)。

6、什么時候線程是活的?
在調(diào)用start()方法開始執(zhí)行線程之前,線程的狀態(tài)還不是活的。測試程序如下:
class ThreadTest implements Runnable {
public void run() {
System.out.println ("someting run here");
}
public static void main (String[] args) {
ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(tt);
System.out.println (t1.isAlive());
t1.start();
System.out.println (t1.isAlive());
}
}

結(jié)果輸出:
false
true
isAlive方法是確定一個線程是否已經(jīng)啟動,而且還沒完成run()方法內(nèi)代碼的最好方法。

7、啟動新線程。
線程的啟動要調(diào)用start()方法,只有這樣才能創(chuàng)建新的調(diào)用棧。而直接調(diào)用run()方法的話,就不會創(chuàng)建新的調(diào)用棧,也就不會創(chuàng)建新的線程,run()方法就與普通的方法沒什么兩樣了!

8、給線程起個有意義的名字。
沒有該線程命名的話,線程會有一個默認(rèn)的名字,格式是:“Thread-”加上線程的序號,如:Thread-0
這看起來可讀性不好,不能從名字分辨出該線程具有什么功能。下面是給線程命名的方式。
第一種:用setName()函數(shù)
第二種:選用帶線程命名的構(gòu)造器
class ThreadTest implements Runnable{
public void run(){
System.out.println (Thread.currentThread().getName());
}
public static void main (String[] args) {
ThreadTest tt = new ThreadTest();
//Thread t = new Thread (tt,"eat apple");
Thread t = new Thread (tt);
t.setName("eat apple");
t.start();
}
}

9、“沒有保障”的多線程的運行。下面的代碼可能令人印象深刻。
class ThreadTest implements Runnable{
public void run(){
System.out.println (Thread.currentThread().getName());
}
public static void main (String[] args) {
ThreadTest tt = new ThreadTest();
Thread[] ts =new Thread[10];

for (int i =0; i < ts.length; i++)
ts[i] = new Thread(tt);

for (Thread t : ts)
t.start();
}
}
在我的電腦上運行的結(jié)果是:
Thread-0
Thread-1
Thread-3
Thread-5
Thread-2
Thread-7
Thread-4
Thread-9
Thread-6
Thread-8
而且每次運行的結(jié)果都是不同的!繼續(xù)引用前面的話,一旦涉及到線程,其運行多半是沒有保障。這個保障是指線程的運行完全是由調(diào)度程序控制的,我們沒法控制它的執(zhí)行順序,持續(xù)時間也沒有保障,有著不可預(yù)料的結(jié)果。


10、線程的狀態(tài)。
A、新狀態(tài)。
實例化Thread對象,但沒有調(diào)用start()方法時的狀態(tài)。
ThreadTest tt = new ThreadTest();
或者Thread t = new Thread (tt);
此時雖然創(chuàng)建了Thread對象,如前所述,但是它們不是活的,不能通過isAlive()測試。

B、就緒狀態(tài)。
線程有資格運行,但調(diào)度程序還沒有把它選為運行線程所處的狀態(tài)。也就是具備了運行的條件,一旦被選中馬上就能運行。
也是調(diào)用start()方法后但沒運行的狀態(tài)。此時雖然沒在運行,但是被認(rèn)為是活的,能通過isAlive()測試。而且在線程運行之后、或者被阻塞、等待或者睡眠狀態(tài)回來之后,線程首先進(jìn)入就緒狀態(tài)。

C、運行狀態(tài)。
從就緒狀態(tài)池(注意不是隊列,是池)中選擇一個為當(dāng)前執(zhí)行進(jìn)程時,該線程所處的狀態(tài)。

D、等待、阻塞、睡眠狀態(tài)。
這三種狀態(tài)有一個共同點:線程依然是活的,但是缺少運行的條件,一旦具備了條就就可以轉(zhuǎn)為就緒狀態(tài)(不能直接轉(zhuǎn)為運行狀態(tài))。另外,suspend()和stop()方法已經(jīng)被廢棄了,比較危險,不要再用了。

E、死亡狀態(tài)。
一個線程的run()方法運行結(jié)束,那么該線程完成其歷史使命,它的棧結(jié)構(gòu)將解散,也就是死亡了。但是它仍然是一個Thread對象,我們?nèi)钥梢砸盟?,就像其他對象一樣!它也不會被垃圾回收器回收了,因為對該對象的引用仍然存在?BR>如此說來,即使run()方法運行結(jié)束線程也沒有死??!事實是,一旦線程死去,它就永遠(yuǎn)不能重新啟動了,也就是說,不能再用start()方法讓它運行起來!如果強(qiáng)來的話會拋出IllegalThreadStateException異常。如:
t.start();
t.start();
放棄吧,人工呼吸或者心臟起搏器都無濟(jì)于事……線程也屬于一次性用品。

11、阻止線程運行。
A、睡眠。sleep()方法
讓線程睡眠的理由很多,比如:認(rèn)為該線程運行得太快,需要減緩一下,以便和其他線程協(xié)調(diào);查詢當(dāng)時的股票價格,每睡5分鐘查詢一次,可以節(jié)省帶寬,而且即時性要求也不那么高。
用Thread的靜態(tài)方法可以實現(xiàn)Thread.sleep(5*60*1000); 睡上5分鐘吧。sleep的參數(shù)是毫秒。但是要注意sleep()方法會拋出檢查異常InterruptedException,對于檢查異常,我們要么聲明,要么使用處理程序。
try {
Thread.sleep(20000);
}
catch (InterruptedException ie) {
ie.printStackTrace();
}
既然有了sleep()方法,我們是不是可以控制線程的執(zhí)行順序了!每個線程執(zhí)行完畢都睡上一覺?這樣就能控制線程的運行順序了,下面是書上的一個例子:
class ThreadTest implements Runnable{
public void run(){
for (int i = 1; i<4; i++){
System.out.println (Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException ie) { }
}
}
public static void main (String[] args) {
ThreadTest tt = new ThreadTest();
Thread t0 = new Thread(tt,"Thread 0");
Thread t1 = new Thread(tt,"Thread 1");
Thread t2 = new Thread(tt,"Thread 2");
t0.start();
t1.start();
t2.start();
}
}

并且給出了結(jié)果:
Thread 0
Thread 1
Thread 2
Thread 0
Thread 1
Thread 2
Thread 0
Thread 1
Thread 2
也就是Thread 0 Thread 1 Thread 2 按照這個順序交替出現(xiàn),作者指出雖然結(jié)果和我們預(yù)料的似乎相同,但是這個結(jié)果是不可靠的。果然被我的雙核電腦驗證了:
Thread 0
Thread 1
Thread 2
Thread 2
Thread 0
Thread 1
Thread 1
Thread 0
Thread 2
看來線程真的很不可靠啊。但是盡管如此,sleep()方法仍然是保證所有線程都有運行機(jī)會的最好方法。至少它保證了一個線程進(jìn)入運行之后不會一直到運行完位置。

時間的精確性。再強(qiáng)調(diào)一下,線程醒來之后不會進(jìn)入運行狀態(tài),而是進(jìn)入就緒狀態(tài)。因此sleep()中指定的時間不是線程不運行的精確時間!不能依賴sleep()方法提供十分精確的定時。我們可以看到很多應(yīng)用程序用sleep()作為定時器,而且沒什么不好的,確實如此,但是我們一定要知道sleep()不能保證線程醒來就能馬上進(jìn)入運行狀態(tài),是不精確的。

sleep()方法是一個靜態(tài)的方法,它所指的是當(dāng)前正在執(zhí)行的線程休眠一個毫秒數(shù)。看到某些書上的Thread.currentThread().sleep(1000); ,其實是不必要的。Thread.sleep(1000);就可以了。類似于getName()方法不是靜態(tài)方法,它必須針對具體某個線程對象,這時用取得當(dāng)前線程的方法Thread.currentThread().getName();

B、線程優(yōu)先級和讓步。
線程的優(yōu)先級。在大多數(shù)jvm實現(xiàn)中調(diào)度程序使用基于線程優(yōu)先級的搶先調(diào)度機(jī)制。如果一個線程進(jìn)入可運行狀態(tài),并且它比池中的任何其他線程和當(dāng)前運行的進(jìn)程的具有更高的優(yōu)先級,則優(yōu)先級較低的線程進(jìn)入可運行狀態(tài),最高優(yōu)先級的線程被選擇去執(zhí)行。

于是就有了這樣的結(jié)論:當(dāng)前運行線程的優(yōu)先級通常不會比池中任何線程的優(yōu)先級低。但是并不是所有的jvm的調(diào)度都這樣,因此一定不能依賴于線程優(yōu)先級來保證程序的正確操作,這仍然是沒有保障的,要把線程優(yōu)先級用作一種提高程序效率的方法,并且這種方法也不能依賴優(yōu)先級的操作。

另外一個沒有保障的操作是:當(dāng)前運行的線程與池中的線程,或者池中的線程具有相同的優(yōu)先級時,JVM的調(diào)度實現(xiàn)會選擇它喜歡的線程。也許是選擇一個去運行,直至其完成;或者用分配時間片的方式,為每個線程提供均等的機(jī)會。

優(yōu)先級用正整數(shù)設(shè)置,通常為1-10,JVM從不會改變一個線程的優(yōu)先級。默認(rèn)情況下,優(yōu)先級是5。Thread類具有三個定義線程優(yōu)先級范圍的靜態(tài)最終常量:Thread.MIN_PRIORITY (為1) Thread.NORM_PRIORITY (為5) Thread.MAX_PRIORITY (為10)

靜態(tài)Thread.yield()方法。
它的作用是讓當(dāng)前運行的線程回到可運行狀態(tài),以便讓具有同等優(yōu)先級的其他線程運行。用yield()方法的目的是讓同等優(yōu)先級的線程能適當(dāng)?shù)剌嗈D(zhuǎn)。但是,并不能保證達(dá)到此效果!因為,即使當(dāng)前變成可運行狀態(tài),可是還有可能再次被JVM選中!也就是連任。

非靜態(tài)join()方法。
讓一個線程加入到另一個線程的尾部。讓B線程加入A線程,意味著在A線程運行完成之前,B線程不會進(jìn)入可運行狀態(tài)。
Thread t = new Thread();
t.start();
t.join;
這段代碼的意思是取得當(dāng)前的線程,把它加入到t線程的尾部,等t線程運行完畢之后,原線程繼續(xù)運行。書中的例子在我的電腦里效果很糟糕,看不出什么效果來。也許是CPU太快了,而且是雙核的;也許是JDK1.6的原因?

12、沒總結(jié)完。線程這部分很重要,內(nèi)容也很多,看太快容易消化不良,偶要慢慢地消化掉……



附: java原著中對線程的解釋。

e文原文:

Thread Scheduling

In Java technology,threads are usually preemptive,but not necessarily Time-sliced(the process of giving each thread an equal amount of CPU time).It is common mistake to believe that "preemptive" is a fancy word for "does time-slicing".

For the runtime on a Solaris Operating Environment platform,Java technology does not preempt threads of the same priority.However,the runtime on Microsoft Windows platforms uses time-slicing,so it preempts threads of the same priority and even threads of higher priority.Preemption is not guaranteed;however,most JVM implementations result in behavior that appears to be strictly preemptive.Across JVM implementations,there is no absolute guarantee of preemption or time-slicing.The only guarantees lie in the coder’s use of wait and sleep.

The model of a preemptive scheduler is that many threads might be runnable,but only one thread is actually running.This thread continues to run until it ceases to be runnable or another thread of higher priority becomes runnable.In the latter case,the lower priority thread is preempted by the thread of higher priority,which gets a chance to run instead.

A thread might cease to runnable (that is,because blocked) for a variety of reasons.The thread’s code can execute a Thread.sleep() call,deliberately asking the thread to pause for a fixed period of time.The thread might have to wait to access a resource and cannot continue until that resource become available.

All thread that are runnable are kept in pools according to priority.When a blocked thread becomes runnable,it is placed back into the appropriate runnable pool.Threads from the highest priority nonempty pool are given CPU time.

The last sentence is worded loosed because:
(1) In most JVM implementations,priorities seem to work in a preemptive manner,although there is no guarantee that priorities have any meaning at all;
(2) Microsoft Window’s values affect thread behavior so that it is possible that a Java Priority 4 thread might be running,in spite of the fact that a runnable Java Priority 5 thread is waiting for the CPU.
In reality,many JVMs implement pool as queues,but this is not guaranteed hehavior.


熱心網(wǎng)友翻譯的版本:

在java技術(shù)中,線程通常是搶占式的而不需要時間片分配進(jìn)程(分配給每個線程相等的cpu時間的進(jìn)程)。一個經(jīng)常犯的錯誤是認(rèn)為“搶占”就是“分配時間片”。
在Solaris平臺上的運行環(huán)境中,相同優(yōu)先級的線程不能相互搶占對方的cpu時間。但是,在使用時間片的windows平臺運行環(huán)境中,可以搶占相同甚至更高優(yōu)先級的線程的cpu時間。搶占并不是絕對的,可是大多數(shù)的JVM的實現(xiàn)結(jié)果在行為上表現(xiàn)出了嚴(yán)格的搶占??v觀JVM的實現(xiàn),并沒有絕對的搶占或是時間片,而是依賴于編碼者對wait和sleep這兩個方法的使用。
搶占式調(diào)度模型就是許多線程屬于可以運行狀態(tài)(等待狀態(tài)),但實際上只有一個線程在運行。該線程一直運行到它終止進(jìn)入可運行狀態(tài)(等待狀態(tài))或是另一個具有更高優(yōu)先級的線程變成可運行狀態(tài)。在后一種情況下,底優(yōu)先級的線程被高優(yōu)先級的線程搶占,高優(yōu)先級的線程獲得運行的機(jī)會。
線程可以因為各種各樣的原因終止并進(jìn)入可運行狀態(tài)(因為堵塞)。例如,線程的代碼可以在適當(dāng)時候執(zhí)行Thread.sleep()方法,故意讓線程中止;線程可能為了訪問資源而不得不等待直到該資源可用為止。
所有可運行的線程根據(jù)優(yōu)先級保持在不同的池中。一旦被堵塞的線程進(jìn)入可運行狀態(tài),它將會被放回適當(dāng)?shù)目蛇\行池中。非空最高優(yōu)先級的池中的線程將獲得cpu時間。
最后一個句子是不精確的,因為:
(1)在大多數(shù)的JVM實現(xiàn)中,雖然不能保證說優(yōu)先級有任何意義,但優(yōu)先級看起來象是用搶占方式工作。
(2)微軟windows的評價影響線程的行為,以至盡管一個處于可運行狀態(tài)的優(yōu)先級為5的java線程正在等待cpu時間,但是一個優(yōu)先級為4的java線程卻可能正在運行。
實際上,許多JVM用隊列來實現(xiàn)池,但沒有保證行為。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多