天天看點

java lang(Thread) 和 Runable接口

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進行操作。