public
interface Runnable {
public abstract void run();
}
public
class Thread implements Runnable {
private Runnable target;
.....
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
....
@Override
public void run() {
if (target != null) {
target.run();
}
}
阻止線程執行
對于線程的阻止,考慮一下三個方面,不考慮IO阻塞的情況:
睡眠;
等待;
因為需要一個對象的鎖定而被阻塞。
1、睡眠
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)靜态方法強制目前正在執行的線程休眠(暫停執行),以“減慢線程”。當線程睡眠時,它入睡在某個地方,在蘇醒之前不會傳回到可運作狀态。當睡眠時間到期,則傳回到可運作狀态。
線程睡眠的原因:線程執行太快,或者需要強制進入下一輪,因為Java規範不保證合理的輪換。
睡眠的實作:調用靜态方法。
try {
Thread.sleep(123);
} catch (InterruptedException e) {
e.printStackTrace();
}
睡眠的位置:為了讓其他線程有機會執行,可以将Thread.sleep()的調用放線程run()之内。這樣才能保證該線程執行過程中會睡眠。
注意:
1、線程睡眠是幫助其他線程獲得運作機會的最好方法。
2、線程睡眠到期自動蘇醒,并傳回到可運作狀态,不是運作狀态。sleep()中指定的時間是線程不會運作的最短時間。是以,sleep()方法不能保證該線程睡眠到期後就開始執行。
3、sleep()是靜态方法,隻能控制目前正在運作的線程。
線程的優先級和線程讓步yield()
線程的讓步是通過Thread.yield()來實作的。yield()方法的作用是:暫停目前正在執行的線程對象,并執行其他線程。
要了解yield(),必須了解線程的優先級的概念。線程總是存在優先級,優先級範圍在1~10之間。JVM線程排程程式是基于優先級的搶先排程機制。在大多數情況下,目前運作的線程優先級将大于或等于線程池中任何線程的優先級。但這僅僅是大多數情況。
注意:當設計多線程應用程式的時候,一定不要依賴于線程的優先級。因為線程排程優先級操作是沒有保障的,隻能把線程優先級作用作為一種提高程式效率的方法,但是要保證程式不依賴這種操作。
當線程池中線程都具有相同的優先級,排程程式的JVM實作自由選擇它喜歡的線程。這時候排程程式的操作有兩種可能:一是選擇一個線程運作,直到它阻塞或者運作完成為止。二是時間分片,為池内的每個線程提供均等的運作機會。
設定線程的優先級:線程預設的優先級是建立它的執行線程的優先級。可以通過setPriority(int newPriority)更改線程的優先級。例如:
Thread t = new MyThread();
t.setPriority(8);
t.start();
線程優先級為1~10之間的正整數,JVM從不會改變一個線程的優先級。然而,1~10之間的值是沒有保證的。一些JVM可能不能識别10個不同的值,而将這些優先級進行每兩個或多個合并,變成少于10個的優先級,則兩個或多個優先級的線程可能被映射為一個優先級。
線程預設優先級是5,Thread類中有三個常量,定義線程優先級範圍:
static int MAX_PRIORITY
線程可以具有的最高優先級。
static int MIN_PRIORITY
線程可以具有的最低優先級。
static int NORM_PRIORITY
配置設定給線程的預設優先級。
3、Thread.yield()方法
Thread.yield()方法作用是:暫停目前正在執行的線程對象,并執行其他線程。
yield()應該做的是讓目前運作線程回到可運作狀态,以允許具有相同優先級的其他線程獲得運作機會。是以,使用yield()的目的是讓相同優先級的線程之間能适當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程排程程式再次選中。
結論:yield()從未導緻線程轉到等待/睡眠/阻塞狀态。在大多數情況下,yield()将導緻線程從運作狀态轉到可運作狀态,但有可能沒有效果。
小結
到目前位置,介紹了線程離開運作狀态的3種方法:
1、調用Thread.sleep():使目前線程睡眠至少多少毫秒(盡管它可能在指定的時間之前被中斷)。
2、調用Thread.yield():不能保障太多事情,盡管通常它會讓目前運作線程回到可運作性狀态,使得有相同優先級的線程有機會執行。
3、調用join()方法:保證目前線程停止執行,直到該線程所加入的線程完成為止。然而,如果它加入的線程沒有存活,則目前線程不需要停止。
除了以上三種方式外,還有下面幾種特殊情況可能使線程離開運作狀态:
1、線程的run()方法完成。
2、在對象上調用wait()方法(不是線上程上調用)。
3、線程不能在對象上獲得鎖定,它正試圖運作該對象的方法代碼。
4、線程排程程式可以決定将目前運作狀态移動到可運作狀态,以便讓另一個線程獲得運作機會,而不需要任何理由。
同步和鎖定
1、鎖的原理
Java中每個對象都有一個内置鎖
當程式運作到非靜态的synchronized同步方法上時,自動獲得與正在執行代碼類的目前執行個體(this執行個體)有關的鎖。獲得一個對象的鎖也稱為擷取鎖、鎖定對象、在對象上鎖定或在對象上同步。
當程式運作到synchronized同步方法或代碼塊時才該對象鎖才起作用。
一個對象隻有一個鎖。是以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或傳回)鎖。這也意味着任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。
釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。
關于鎖和同步,有一下幾個要點:
1)、隻能同步方法,而不能同步變量和類;
2)、每個對象隻有一個鎖;當提到同步時,應該清楚在什麼上同步?也就是說,在哪個對象上同步?
3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4)、如果兩個線程要執行一個類中的synchronized方法,并且兩個線程使用相同的執行個體來調用方法,那麼一次隻能有一個線程能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖,就沒有任何其他線程可以進入(該對象的)類中的任何一個同步方法。
5)、如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由通路而不受鎖的限制。
6)、線程睡眠時,它所持的任何鎖都不會釋放。
7)、線程可以獲得多個鎖。比如,在一個對象的同步方法裡面調用另外一個對象的同步方法,則擷取了兩個對象的同步鎖。
8)、同步損害并發性,應該盡可能縮小同步範圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。
9)、在使用同步代碼塊時候,應該指定在哪個對象上同步,也就是說要擷取哪個對象的鎖。
三、靜态方法同步
要同步靜态方法,需要一個用于整個類對象的鎖,這個對象是就是這個類(XXX.class)。
例如:
public static synchronized int setName(String name){
Xxx.name = name;
}
等價于
public static int setName(String name){
synchronized(Xxx.class){
Xxx.name = name;
}
sleep()和wait()的差別
Java中的多線程是一種搶占式的機制而不是分時機制。線程主要有以下幾種狀态:可運作,運作,阻塞,死亡。搶占式機制指的是有多個線程處于可運作狀态,但是隻有一個線程在運作。
當有多個線程通路共享資料的時候,就需要對線程進行同步。線程中的幾個主要方法的比較:
Thread類的方法:sleep(),yield()等
Object的方法:wait()和notify()等
每個對象都有一個機鎖來控制同步通路。Synchronized關鍵字可以和對象的機鎖互動,來實作線程的同步。
由于sleep()方法是Thread類的方法,是以它不能改變對象的機鎖。是以當在一個Synchronized方法中調用sleep()時,線程雖然休眠了,但是對象的機鎖沒有被釋放,其他線程仍然無法通路這個對象。而wait()方法則會線上程休眠的同時釋放掉機鎖,其他線程可以通路該對象。
Yield()方法是停止目前線程,讓同等優先權的線程運作。如果沒有同等優先權的線程,那麼Yield()方法将不會起作用。
一個線程結束的标志是:run()方法結束。
一個機鎖被釋放的标志是:synchronized塊或方法結束。
Wait()方法和notify()方法:當一個線程執行到wait()方法時(線程休眠且釋放機鎖),它就進入到一個和該對象相關的等待池中,同時失去了對象的機鎖。當它被一個notify()方法喚醒時,等待池中的線程就被放到了鎖池中。該線程從鎖池中獲得機鎖,然後回到wait()前的中斷現場。
join()方法使目前線程停下來等待,直至另一個調用join方法的線程終止。
值得注意的是:線程的在被激活後不一定馬上就運作,而是進入到可運作線程的隊列中。
共同點: 他們都是在多線程的環境下,都可以在程式的調用處阻塞指定的毫秒數,并傳回。
不同點: Thread.sleep(long)可以不在synchronized的塊下調用,而且使用Thread.sleep()不會丢失目前線程對任何對象的同步鎖(monitor);
object.wait(long)必須在synchronized的塊下來使用,調用了之後失去對object的monitor, 這樣做的好處是它不影響其它的線程對object進行操作。