線程基礎----轉

創建

在Java中要實現執行緒功能,可以實作Runnable介面,Runnable介面中只定義一個run()方法,然後實例化一個 Thread物件時,傳入一個實作Runnable介面的物件作為引數,Thread物件會調用Runnable物件的run()方法,進而執行當中所定義的流程
調用方法:
1
x implement Runable
Runable x = new x();
Thread t = new Thread(x);

2匿名方式
Thread thread1 = new Thread(new Runnable() {
public void run() {
});
Thread類別也實作了Runnable介面,您也可以繼承Thread類別並重新定義它的run()方法,好處是可以使用Thread上的一些繼承下來的方法,例如yield(),然而繼承了Thread就表示您不能讓您的類別再繼承其它的類別。

public class MyThread extends Thread {
public run() {
System.out.println(”MyThread.run()”);
}
}

Daemon Thread

主執行緒結束之後,Daemon也就跟著結束.

使用setDaemon()方法來設定一個執行緒是否為Daemon執行緒,使用isDaemon()方法則可以判斷該執行緒是否為Daemon執行緒。

線程生命週期

線程生命週期(轉自臺灣)

雖然執行緒看起來像是同時執行,但事實上同一時間點上,還是隻有一個執行緒在動作,只是執行緒之間切換的動作很快,所以看來像是同時執行。執行緒有其優先權,由1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY),預設是 Thread.NORM_PRIORITY(5)。
優先權高的執行緒會先被執行完畢,然後才會輪到優先權低的執行緒,如果優先權相同,則輸流執行(Round-robin方式)。
決大多數的作業系統都支援timeslicing,簡單的說就是作業系統會為每個執行緒分配一小段CPU時間(quantum),時間一到就換下一個執行緒,即使現有的執行緒還沒結束。對於不支援timeslicing的作業系統,每一個執行緒必須完成後,才能輪到下一個執行緒,在這樣的作業系統中,如果您想要讓目前執行緒禮讓一下其它執行緒,讓它們有機會取得執行權,您可以在呼叫緒行緒的yield()方法,例如:

public class SomeClass {
// …..
Thread thread = new Thread(new Runnable() {
public void run() {
// ….
while(true) {
// ….
Thread.yield(); // 暫時讓出執行權
}
}
});
thread.start();
// ….
}

yield()方法讓同樣優先權的執行緒有被執行的機會,當執行緒執行yield()方法讓出執行權時,它會再度加入執行緒的排班,等待再度取得執行權,對於支援timeslicing的作業系統,呼叫yield()是不太需要的,因為作業系統會自動分配時間給執行緒輪流執行。

有幾種狀況會讓執行緒進入Not Runnable狀態(或是blocked狀態):

呼叫sleep()
呼叫wait()
等待I/O完成

當執行緒在Not Runnable狀態時,執行緒是可以被執行的,但有某些原因阻止它執行(例如等待使用者的輸入),執行緒排班器將不分配執行時間給這個執行緒,直到以下的幾個情況讓執行緒回到Runnable狀態:

執行緒呼叫notify()
執行緒呼叫notifyAll()
執行緒呼叫interrupt()

當執行緒因為I/O而進入blocked狀態,它必須等到I/O完成纔可以離開這個狀態。

最後,如果執行的工作完成(或發生例外)而離開run()方法,則執行緒執行完畢,進入Dead狀態,您可以使用isAlive()方法來測試執行緒是否存活。

如果您查詢Java的線上API文件,您會發現有suspend()、resume()、stop()等方法,這些方法Java並不建議您使用,而且已經被標示為”deprecated”,這些方法建議您在需要的時候自行實作。
當您使用Thread.sleep()讓執行緒暫停執行進入Not Runnable狀態,您可以使用interrupt()讓它離開Not Runnable狀態,當使用sleep()暫時進入Not Runnable狀態而您interrupt()時,會丟出InterruptedException例外物件.

sleep和wait的區別: sleep()不釋放同步鎖,wait()釋放同步鎖

sleep()方法是使線程停止一段時間的方法。在sleep 時間間隔期滿後,線程不一定立即恢復執行。這是因爲在那個時刻,其它線程可能正在運行而且沒有被調度爲放棄執行,除非
(a)“醒來”的線程具有更高的優先級。
  (b)正在運行的線程因爲其它原因而阻塞。

 wait()是線程交互時,如果線程對一個同步對象x 發出一個wait()調用,該線程會暫停執行,被調對象進入等待狀態,直到被喚醒或等待時間到。
當調用wait()後,線程會釋放掉它所佔有的“鎖標誌”,從而使線程所在對象中的其它synchronized數據可被別的線程使用。
waite()和notify()因爲會對對象的“鎖標誌”進行操作,所以它們必須在synchronized函數或synchronized block中進行調用。如果在non-synchronized函數或non-synchronized block中進行調用,雖然能編譯通過,但在運行時會發生IllegalMonitorStateException的異常。

join 函數
如果有一個A執行緒正在運行,您希望插入一個B執行緒,並要求B執行緒先執行完畢,然後再繼續A執行緒的流程,您可以使用 join()方法來完成這個需求,這就好比您手頭上正有一個工作在進行,老闆插入一個工作要求您先作好,然後再進行您原先正進行的工作。

線程組

在Java中每個執行緒都屬於某個「執行緒羣組」(ThreadGroup)管理的一員,例如若您是在main ()主工作流程中產生一個執行緒,則產生的執行緒屬於main這個執行緒羣組管理的一員,您可以使用下面的指令來取得目前執行緒所屬的執行緒羣組名稱::

Thread.currentThread().getThreadGroup().getName();

每一個執行緒產生時,都會被歸入某個執行緒羣組,這視您的執行緒是在哪個羣組中產生,如果沒有指定,則歸入產生該子執行緒的執行緒羣組中,您也可以自行指定執行緒羣組,執行緒一但歸入某個羣組,就無法更換羣組。
ThreadGroup中的某些方法,可以對所有的執行緒產生作用,例如interrupt()可以interrupt羣組中所有的線程.

synchronized
一、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。
二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。

“synchronized”的一個使用方式是用於方法上,讓方法作用範圍內都成為被同步化區域,例如:

public synchronized void setNameAndID(String name,
String id) {
this.name = name;
this.id = id;
if(!checkNameAndIDEqual()) {
System.out.println(count +
“) illegal name or ID…..”);
}
count++;
}

每個物件內部都會有一個鎖定(lock),當執行緒執行某個物件的同步化方法時,它會在物件上得到這個鎖定,只有取得鎖定的執行緒纔可進入同步區,未取得鎖定的執行緒則必須等待,直到有機會取得鎖定,其它執行緒必須等目前執行緒先執行完同步化方法,並解除對物件的鎖定,纔有機會取得物件上的鎖定。

就這個例子來說,簡單的說,就是有執行緒在執行setNameAndID()時,會從物件上取得鎖定,其它執行緒必須等待它執行完畢,釋放鎖定之後,才會有機會競爭鎖定,取得鎖定的執行緒纔可以執行setNameAndID ()。

以上所介紹的是實例方法同步化(instance method synchronized),同步化的設定不只可用於方法上,也可以用於某個程式區塊上,稱之為實例區塊同步化(instance block synchronized),例如:

public void setNameAndID(String name, String id) {
synchronized(this) {
this.name = name;
this.id = id;
if(!checkNameAndIDEqual()) {
System.out.println(count +
“) illegal name or ID…..”);
}
count++;
}
}

上面的意思就是在執行緒執行至”synchronized”設定的區塊時取得物件的鎖定,這麼一來其它執行緒暫時無法取得鎖定,因此無法執行物件同步化區塊,這個方式可以應用於您不想鎖定整個方法區塊,而只是想在共享資料在被執行緒存取時確保同步化時,由於只鎖定方法中的某個區塊,在執行完區塊後即釋放對物件的鎖定,以便讓其它執行緒有機會取得鎖定,對物件進行操作,在某些時候會比較有效率。

實例區塊同步化的好處是,您也可以對某個物件進行同步化,而像實例方法同步化只針對this,例如在多執行緒存取同一個ArrayList物件時,ArrayList並沒有實作資料存取時的同步化,所以它使用於多執行緒時,必須注意是否必須對它進行同步化,多個執行緒存取同一個ArrayList時,有可能發生兩個以上的執行緒將資料存入 ArrayList的同一個位置,造成資料的相互覆蓋,為了確保資料存入時的正確性,您可以在存取ArrayList物件時對它進行同步化,例如:

// arraylist參考至一個ArrayList的一個實例
synchronized(arraylist) {
arraylist.add(new SomeClass());
}

除了針對物件同步之外,您還可以針對靜態方法同步化(static method synchronized),例如某個static成員會被多執行緒存取時,則可以如下設定:
public class Some {
private static int value;

public synchronized static void some() {
value++;
….
}
}

進行鎖定時,會鎖定Some.class,因而static成員也受到保護。類似於實例區塊同步化,您也可以在區塊中鎖定整個類別,稱之為類別字面同步化(class literals synchronized),例如:

public void doSomething() {
synchronized(Some.class) {
….
}
}

事實上,您也可以使用Collections的synchronizedXXX()等方法來傳回一個同步化的容器物件,例如傳回一個同步化的List:

List list = Collections.synchronizedList(new ArrayList());

同步化所犧性的自然就是在於執行緒等待時的延遲,所以同步化的手法不應被濫用,您不用將整個物件的方法都加上”synchronized”,有些方法只是單純的傳回某些數值,它並沒有對共用資料進行修改的動作,那麼它就不需要被同步化。

notify and wait
wait()、notify()與notifyAll()是由 Object所提供的方法,您在定義自己的類別時會繼承下來(記得Java中所有的物件最頂層都繼承自Object),wait()、notify()與 notifyAll()都被宣告為”final”,所以您無法重新定義它們,透過這三個方法您可以控制執行緒是否為Runnable狀態。

您必須在同步化的方法或區塊中呼叫wait()方法,當物件的wait()方法被調用,目前的執行緒會被放入物件的「等待集合」(Wait set)中,執行緒會釋放物件的鎖定,其它的執行緒可以競爭鎖定,取得鎖定的執行緒可以執行同步化區塊;被放在等待集中的執行緒將不參與執行緒的排班,wait()可以指定等待的時間,如果指定時間的話,則時間到之後執行緒會再度加入排班,如果指定時間0或不指定,則執行緒會持續等待,直到有被中斷(interrupt)或是被告知(notify)可以參與排班。

當物件的notify()被調用,它會從物件的等待集中選出「一個」執行緒加入排班,被選出的執行緒是隨機的,被選出的執行緒會與其它正在執行的執行緒共同競爭對物件的鎖定;如果您呼叫notifyAll(),則「所有」在等待集中的執行緒都會被喚醒,這些執行緒會與其它正在執行的執行緒共同競爭對物件的鎖定。

簡單的說,當執行緒呼叫到物件的wait()方法時,表示它要先讓出物件的被同步區使用權並等待通知,或是等待一段指定的時間,直到被通知或時間到時再從等待點開始執行,這就好比您要叫某人作事,作到一半時某人叫您等候通知(或等候1分鐘之類的),當您被通知(或時間到時)某人會繼承為您服務。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章