程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> JAVA-多線程

JAVA-多線程

編輯:JAVA綜合教程

JAVA-多線程


Java線程:概念與原理
一、操作系統中線程和進程的概念
現在的操作系統是多任務操作系統。多線程是實現多任務的一種方式。
進程是指一個內存中運行的應用程序,每個進程都有自己獨立的一塊內存空間,一個進程中可以啟動多個線程。比如在Windows系統中,一個運行的exe就是一個進程
線程是指進程中的一個執行流程,一個進程中可以運行多個線程。比如java.exe進程中可以運行很多線程。線程總是屬於某個進程,進程中的多個線程共享進程的內存。
“同時”執行是人的感覺,在線程之間實際上輪換執行。

二、Java中的線程
在Java中,“線程”指兩件不同的事情:
1–java.lang.Thread類的一個實例;
2–線程的執行

3–JAVA多線程實現方式主要有三種:繼承Thread類、實現Runnable接口、使用ExecutorService、Callable、Future實現有返回結果的多線程。其中前兩種方式線程執行完後都沒有返回值,只有最後一種是帶返回值的!

使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來定義、實例化和啟動新線程。

一個Thread類實例只是一個對象,像Java中的任何其他對象一樣,具有變量和方法,生死於堆上。

Java中,每個線程都有一個調用棧,即使不在程序中創建任何新的線程,線程也在後台運行著。

一個Java應用總是從main()方法開始運行,mian()方法運行在一個線程內,它被稱為主線程。

一旦創建一個新的線程,就產生一個新的調用棧。

線程總體分兩類:用戶線程和守候線程。

當所有用戶線程執行完畢的時候,JVM自動關閉。但是守候線程卻不獨立於JVM,守候線程一般是由操作系統或者用戶自己創建的
Java線程:創建與啟動
一、定義線程

1、擴展java.lang.Thread類。

此類中有個run()方法,應該注意其用法:
public void run()
如果該線程是使用獨立的Runnable運行對象構造的,則調用該Runnable對象的run方法;否則,該方法不執行任何操作並返回。

Thread的子類應該重寫該方法。
2、實現java.lang.Runnable接口。

void run()
使用實現接口Runnable的對象創建一個線程時,啟動該線程將導致在獨立執行的線程中調用對象的run方法。

方法run的常規協定是,它可能執行任何所需的操作。

二、實例化線程

1、如果是擴展java.lang.Thread類的線程,則直接new即可。

2、如果是實現了java.lang.Runnable接口的類,則用Thread的構造方法:

Thread(Runnable target) 
Thread(Runnable target, String name) 
Thread(ThreadGroup group, Runnable target) 
Thread(ThreadGroup group, Runnable target, String name) 
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

三、啟動線程

在線程的Thread對象上調用start()方法,而不是run()或者別的方法。

在調用start()方法之前:線程處於新狀態中,新狀態指有一個Thread對象,但還沒有一個真正的線程。

在調用start()方法之後:發生了一系列復雜的事情
啟動新的執行線程(具有新的調用棧);
該線程從新狀態轉移到可運行狀態;
當該線程獲得機會執行時,其目標run()方法將運行。

注意:對Java來說,run()方法沒有任何特別之處。像main()方法一樣,它只是新線程知道調用的方法名稱(和簽名)。因此,在Runnable上或者Thread上調用run方法是合法的。但並不啟動新的線程。

四、例子

public class MyThred extends Thread {
public void run() {
super.run();
System.out.print("skynote");
  }
}


public static  void main(String[] args){
       MyThred myThred=new MyThred();
       myThred.start();
}

2–實現Runnable接口實現多線程
如果自己的類已經extends另一個類,就無法直接extends Thread,此時,必須實現一個Runnable接口

public class MyThred extends OtherClass implements Runnable {

public void run() {
System.out.print("skynote");
}
}

MyThred myThred=new MyThred();
Thread thread=new Thread(myThred);
thread.start();

五、一些常見問題

1、線程的名字,一個運行中的線程總是有名字的,名字有兩個來源,一個是虛擬機自己給的名字,一個是你自己的定的名字。在沒有指定線程名字的情況下,虛擬機總會為線程指定名字,並且主線程的名字總是mian,非主線程的名字不確定。
2、線程都可以設置名字,也可以獲取線程的名字,連主線程也不例外。
3、獲取當前線程的對象的方法是:Thread.currentThread();
4、在上面的代碼中,只能保證:每個線程都將啟動,每個線程都將運行直到完成。一系列線程以某種順序啟動並不意味著將按該順序執行。對於任何一組啟動的線程來說,調度程序不能保證其執行次序,持續時間也無法保證。
5、當線程目標run()方法結束時該線程完成。
6、一旦線程啟動,它就永遠不能再重新啟動。只有一個新的線程可以被啟動,並且只能一次。一個可運行的線程或死線程可以被重新啟動。
7、線程的調度是JVM的一部分,在一個CPU的機器上上,實際上一次只能運行一個線程。一次只有一個線程棧執行。JVM線程調度程序決定實際運行哪個處於可運行狀態的線程。
眾多可運行線程中的某一個會被選中做為當前線程。可運行線程被選擇運行的順序是沒有保障的。
8、盡管通常采用隊列形式,但這是沒有保障的。隊列形式是指當一個線程完成“一輪”時,它移到可運行隊列的尾部等待,直到它最終排隊到該隊列的前端為止,它才能被再次選中。事實上,我們把它稱為可運行池而不是一個可運行隊列,目的是幫助認識線程並不都是以某種有保障的順序排列唱呢個一個隊列的事實。
9、盡管我們沒有無法控制線程調度程序,但可以通過別的方式來影響線程調度的方式!

Java線程:線程棧模型與線程的變量
線程棧是指某時刻時內存中線程調度的棧信息,當前調用的方法總是位於棧頂。線程棧的內容是隨著程序的運行動態變化的,因此研究線程棧必須選擇一個運行的時刻(實際上指代碼運行到什麼地方)。

Java線程:線程狀態的轉換
一、線程狀態

線程的狀態轉換是線程控制的基礎。線程狀態總的可分為五大狀態:分別是生、死、可運行、運行、等待/阻塞!

1、新狀態:線程對象已經創建,還沒有在其上調用start()方法

2、可運行狀態:當線程有資格運行,但調度程序還沒有把它選定為運行線程時線程所處的狀態。當start()方法調用時,線程首先進入可運行狀態。在線程運行之後或者從阻塞、等待或睡眠狀態回來後,也返回到可運行狀態。

3、運行狀態:線程調度程序從可運行池中選擇一個線程作為當前線程時線程所處的狀態。這也是線程進入運行狀態的唯一一種方式。

4、等待/阻塞/睡眠狀態:這是線程有資格運行時它所處的狀態。實際上這個三狀態組合為一種,其共同點是:線程仍舊是活的,但是當前沒有條件運行。換句話說,它是可運行的,但是如果某件事件出現,他可能返回到可運行狀態。

5、死亡態:當線程的run()方法完成時就認為它死去。這個線程對象也許是活的,但是,它已經不是一個單獨執行的線程。線程一旦死亡,就不能復生。如果在一個死去的線程上調用start()方法,會拋出java.lang.IllegalThreadStateException異常。

二、阻止線程執行
對於線程的阻止,考慮一下三個方面,不考慮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()是靜態方法,只能控制當前正在運行的線程。
4、線程的優先級和線程讓步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()將導致線程從運行狀態轉到可運行狀態,但有可能沒有效果。

4、join()方法

Thread的非靜態方法join()讓一個線程B“加入”到另外一個線程A的尾部。在A執行完畢之前,B不能工作。例如:

 Thread t = new MyThread();
        t.start();
        t.join();

另外,join()方法還有帶超時限制的重載版本。例如t.join(5000);則讓線程等待5000毫秒,如果超過這個時間,則停止等待,變為可運行狀態。

線程的加入join()對線程棧導致的結果是線程棧發生了變化,當然這些變化都是瞬時的!

小結
到目前位置,介紹了線程離開運行狀態的3種方法:
1、調用Thread.sleep():使當前線程睡眠至少多少毫秒(盡管它可能在指定的時間之前被中斷)。
2、調用Thread.yield():不能保障太多事情,盡管通常它會讓當前運行線程回到可運行性狀態,使得有相同優先級的線程有機會執行。
3、調用join()方法:保證當前線程停止執行,直到該線程所加入的線程完成為止。然而,如果它加入的線程沒有存活,則當前線程不需要停止。

除了以上三種方式外,還有下面幾種特殊情況可能使線程離開運行狀態:
1、線程的run()方法完成。
2、在對象上調用wait()方法(不是在線程上調用)。
3、線程不能在對象上獲得鎖定,它正試圖運行該對象的方法代碼。
4、線程調度程序可以決定將當前運行狀態移動到可運行狀態,以便讓另一個線程獲得運行機會,而不需要任何理由。
Java線程:線程的同步與鎖
線程的同步是為了防止多個線程訪問一個數據對象時,對數據造成的破壞。
1、鎖的原理

Java中每個對象都有一個內置鎖

當程序運行到非靜態的synchronized同步方法上時,自動獲得與正在執行代碼類的當前實例(this實例)有關的鎖。獲得一個對象的鎖也稱為獲取鎖、鎖定對象、在對象上鎖定或在對象上同步。

當程序運行到synchronized同步方法或代碼塊時才該對象鎖才起作用。

一個對象只有一個鎖。所以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味著任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。

釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。

關於鎖和同步,有一下幾個要點:
1)、只能同步方法,而不能同步變量和類;
2)、每個對象只有一個鎖;當提到同步時,應該清楚在什麼上同步?也就是說,在哪個對象上同步?
3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4)、如果兩個線程要執行一個類中的synchronized方法,並且兩個線程使用相同的實例來調用方法,那麼一次只能有一個線程能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖,就沒有任何其他線程可以進入(該對象的)類中的任何一個同步方法。
5)、如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由訪問而不受鎖的限制。
6)、線程睡眠時,它所持的任何鎖都不會釋放。
7)、線程可以獲得多個鎖。比如,在一個對象的同步方法裡面調用另外一個對象的同步方法,則獲取了兩個對象的同步鎖。
8)、同步損害並發性,應該盡可能縮小同步范圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。
9)、在使用同步代碼塊時候,應該指定在哪個對象上同步,也就是說要獲取哪個對象的鎖。例如:

 public int fix(int y) {
        synchronized (this) {
            x = x - y;
        }
        return x;
    }

當然,同步方法也可以改寫為非同步方法,但功能完全一樣的,例如:

public synchronized int getX() {
        return x+;
    }
與
    public int getX() {
        synchronized (this) {
            return x;
        }
    }

效果是完全一樣的。

三、靜態方法同步

要同步靜態方法,需要一個用於整個類對象的鎖,這個對象是就是這個類(XXX.class)。
例如:

public static synchronized int setName(String name){
      Xxx.name = name;
}
等價於
public static int setName(String name){
      synchronized(Xxx.class){
            Xxx.name = name;
      }
}

四、如果線程不能不能獲得鎖會怎麼樣

如果線程試圖進入同步方法,而其鎖已經被占用,則線程在該對象上被阻塞。實質上,線程進入該對象的的一種池中,必須在哪裡等待,直到其鎖被釋放,該線程再次變為可運行或運行為止。

當考慮阻塞時,一定要注意哪個對象正被用於鎖定:
1、調用同一個對象中非靜態同步方法的線程將彼此阻塞。如果是不同對象,則每個線程有自己的對象的鎖,線程間彼此互不干預。

2、調用同一個類中的靜態同步方法的線程將彼此阻塞,它們都是鎖定在相同的Class對象上。

3、靜態同步方法和非靜態同步方法將永遠不會彼此阻塞,因為靜態方法鎖定在Class對象上,非靜態方法鎖定在該類的對象上。

4、對於同步代碼塊,要看清楚什麼對象已經用於鎖定(synchronized後面括號的內容)。在同一個對象上進行同步的線程將彼此阻塞,在不同對象上鎖定的線程將永遠不會彼此阻塞。

五、何時需要同步

在多個線程同時訪問互斥(可交換)數據時,應該同步以保護數據,確保兩個線程不會同時修改更改它。

對於非靜態字段中可更改的數據,通常使用非靜態方法訪問。
對於靜態字段中可更改的數據,通常使用靜態方法訪問。

如果需要在非靜態方法中使用靜態字段,或者在靜態字段中調用非靜態方法,問題將變得非常復雜。已經超出SJCP考試范圍了。

六、線程安全類

當一個類已經很好的同步以保護它的數據時,這個類就稱為“線程安全的”。

即使是線程安全類,也應該特別小心,因為操作的線程是間仍然不一定安全。

舉個形象的例子,比如一個集合是線程安全的,有兩個線程在操作同一個集合對象,當第一個線程查詢集合非空後,刪除集合中所有元素的時候。第二個線程也來執行與第一個線程相同的操作,也許在第一個線程查詢後,第二個線程也查詢出集合非空,但是當第一個執行清除後,第二個再執行刪除顯然是不對的,因為此時集合已經為空了!

七、線程死鎖

死鎖對Java程序來說,是很復雜的,也很難發現問題。當兩個線程被阻塞,每個線程在等待另一個線程時就發生死鎖!

八、線程同步小結

1、線程同步的目的是為了保護多個線程反問一個資源時對資源的破壞。
2、線程同步方法是通過鎖來實現,每個對象都有切僅有一個鎖,這個鎖與一個特定的對象關聯,線程一旦獲取了對象鎖,其他訪問該對象的線程就無法再訪問該對象的其他同步方法。
3、對於靜態同步方法,鎖是針對這個類的,鎖對象是該類的Class對象。靜態和非靜態方法的鎖互不干預。一個線程獲得鎖,當在一個同步方法中訪問另外對象上的同步方法時,會獲取這兩個對象鎖。
4、對於同步,要時刻清醒在哪個對象上同步,這是關鍵。
5、編寫線程安全的類,需要時刻注意對多個線程競爭訪問資源的邏輯和安全做出正確的判斷,對“原子”操作做出分析,並保證原子操作期間別的線程無法訪問競爭資源。
6、當多個線程等待一個對象鎖時,沒有獲取到鎖的線程將發生阻塞。
7、死鎖是線程間相互等待鎖鎖造成的,在實際中發生的概率非常的小。真讓你寫個死鎖程序,不一定好使,呵呵。但是,一旦程序發生死鎖,程序將死掉。
Java線程:線程的交互
編寫代碼來恰當使用等待、通知和通知所有線程。

一、線程交互的基礎知識
線程交互知識點需要從java.lang.Object的類的三個方法來學習:

void notify()
喚醒在此對象監視器上等待的單個線程。
void notifyAll()
喚醒在此對象監視器上等待的所有線程。
void wait()
導致當前的線程等待,直到其他線程調用此對象的 notify()方法或 notifyAll()方法。

當然,wait()還有另外兩個重載方法:
void wait(long timeout)
導致當前的線程等待,直到其他線程調用此對象的 notify()方法或 notifyAll()方法,或者超過指定的時間量。
void wait(long timeout, int nanos)
導致當前的線程等待,直到其他線程調用此對象的 notify()方法或 notifyAll()方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量。

以上這些方法是幫助線程傳遞線程關心的時間狀態。

關於等待/通知,要記住的關鍵點是:
必須從同步環境內調用wait()、notify()、notifyAll()方法。線程不能調用對象上等待或通知的方法,除非它擁有那個對象的鎖!
wait()、notify()、notifyAll()都是Object的實例方法。與每個對象具有鎖一樣,每個對象可以有一個線程列表,他們等待來自該信號(通知)。線程通過執行對象上的wait()方法獲得這個等待列表。從那時候起,它不再執行任何其他指令,直到調用對象的notify()方法為止。如果多個線程在同一個對象上等待,則將只選擇一個線程(不保證以何種順序)繼續執行。如果沒有線程等待,則不采取任何特殊操作。

public      class ThreadA {
    public  static    void  main(String[] args) {
        ThreadB b = new ThreadB();
        b.start(); 
        //線程A擁有b對象上的鎖。線程為了調用wait()或notify()方法,該線程必須是那個對象鎖的擁有者
        synchronized (b) {
            try {
                System.out.println("");
                //當前線程A等待
                b.wait(); 
            } catch (InterruptedException e) {
                e.printStackTrace(); 
            } 
            System.out.println("" + b.total);
        } 
    } 
}

publi  cclass ThreadB   extends Thread {
    int total; 
    publicvoid run() {
        synchronized (this) {
            for (int i = 0; i < 101; i++) {
                total += i; 
            } 
            notify(); 
        } 
    } 
}

千萬注意:
當在對象上調用wait()方法時,執行該代碼的線程立即放棄它在對象上的鎖。然而調用notify()時,並不意味著這時線程會放棄其鎖。如果線程榮然在完成同步代碼,則線程在移出之前不會放棄鎖。因此,只要調用notify()並不意味著這時該鎖變得可用!

二、多個線程在等待一個對象鎖時候使用notifyAll()

在多數情況下,最好通知等待某個對象的所有線程。如果這樣做,可以在對象上使用notifyAll()讓所有在此對象上等待的線程沖出等待區,返回到可運行狀態。

下面給個例子:

public   class Calculator     extends Thread {
        int total;
        public   void run() {
                synchronized (this) {
                        for (int i = 0; i < 101; i++) {
                                total += i; 
                        } 
                }
                notifyAll(); 
        } 
}

public    class ReaderResult   extends Thread {
        Calculator c; 

        public ReaderResult(Calculator c) {
                this.c = c;
        } 

        publicvoid run() {
                synchronized (c) {
                        try {
                                System.out.println(Thread.currentThread() + "");
                                c.wait(); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                        System.out.println(Thread.currentThread() + "" + c.total);
                } 
        } 

        publicstaticvoid main(String[] args) {
                Calculator calculator = new Calculator();
                new ReaderResult(calculator).start();
                new ReaderResult(calculator).start();
                new ReaderResult(calculator).start();
                calculator.start(); 
        } 
}

運行結果表明,程序中有異常,並且多次運行結果可能有多種輸出結果。這就是說明,這個多線程的交互程序還存在問題。究竟是出了什麼問題,需要深入的分析和思考,下面將做具體分析。

實際上,上面這個代碼中,我們期望的是讀取結果的線程在計算線程調用notifyAll()之前等待即可。但是,如果計算線程先執行,並在讀取結果線程等待之前調用了notify()方法,那麼又會發生什麼呢?這種情況是可能發生的。因為無法保證線程的不同部分將按照什麼順序來執行。幸運的是當讀取線程運行時,它只能馬上進入等待狀態—-它沒有做任何事情來檢查等待的事件是否已經發生。 —-因此,如果計算線程已經調用了notifyAll()方法,那麼它就不會再次調用notifyAll(),—-並且等待的讀取線程將永遠保持等待。這當然是開發者所不願意看到的問題。

因此,當等待的事件發生時,需要能夠檢查notifyAll()通知事件是否已經發生。

通常,解決上面問題的最佳方式是將
Java線程:線程的調度-休眠
Java線程調度是Java多線程的核心,只有良好的調度,才能充分發揮系統的性能,提高程序的執行效率。

這裡要明確的一點,不管程序員怎麼編寫調度,只能最大限度的影響線程執行的次序,而不能做到精准控制。

線程休眠的目的是使線程讓出CPU的最簡單的做法之一,線程休眠時候,會將CPU資源交給其他線程,以便能輪換執行,當休眠一定時間後,線程會蘇醒,進入准備狀態等待執行。

線程休眠的方法是Thread.sleep(long millis)和Thread.sleep(long millis, int nanos),均為靜態方法,那調用sleep休眠的哪個線程呢?簡單說,哪個線程調用sleep,就休眠哪個線程。

public   class Test {
        public    static     void main(String[] args) {
                Thread t1 = new MyThread1();
                Thread t2 = new Thread(new MyRunnable());
                t1.start(); 
                t2.start(); 
        } 
} 

class MyThread1 extends Thread { 
        publicvoid run() {
                for (int i = 0; i < 3; i++) {
                        System.out.println("線程1第" + i + "次執行!");
                        try {
                                Thread.sleep(50); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
} 

class MyRunnable     implements Runnable {
        publicvoid run() {
                for (int i = 0; i < 3; i++) {
                        System.out.println("線程2第" + i + "次執行!");
                        try {
                                Thread.sleep(50); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
}

Java線程:線程的調度-優先級
與線程休眠類似,線程的優先級仍然無法保障線程的執行次序。只不過,優先級高的線程獲取CPU資源的概率較大,優先級低的並非沒機會執行!

線程的優先級用1-10之間的整數表示,數值越大優先級越高,默認的優先級為5。

在一個線程中開啟另外一個新線程,則新開線程稱為該線程的子線程,子線程初始優先級與父線程相同。

public   class Test {
        public     staticv    oid main(String[] args) {
                Thread t1 = new MyThread1();
                Thread t2 = new Thread(new MyRunnable());
                t1.setPriority(10); 
                t2.setPriority(1); 

                t2.start(); 
                t1.start(); 
        } 
} 

class MyThread1 extends Thread { 
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("線程1第" + i + "次執行!");
                        try {
                                Thread.sleep(100); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
} 

class MyRunnableimplements Runnable {
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("線程2第" + i + "次執行!");
                        try {
                                Thread.sleep(100); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
}

Java線程:線程的調度-讓步
線程的讓步含義就是使當前運行著線程讓出CPU資源,但是然給誰不知道,僅僅是讓出,線程狀態回到可運行狀態。

線程的讓步使用Thread.yield()方法,yield()為靜態方法,功能是暫停當前正在執行的線程對象,並執行其他線程。

public   class Test {
        public     static    void main(String[] args) {
                Thread t1 = new MyThread1();
                Thread t2 = new Thread(new MyRunnable());

                t2.start(); 
                t1.start(); 
        } 
} 

class MyThread1 extends Thread { 
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("線程1第" + i + "次執行!");
                } 
        } 
} 

class MyRunnableimplements Runnable {
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("線程2第" + i + "次執行!");
                        Thread.yield(); 
                } 
        } 
}

Java線程:線程的調度-合並
線程的合並的含義就是將幾個並行線程的線程合並為一個單線程執行,應用場景是當一個線程必須等待另一個線程執行完畢才能執行時可以使用join方法。

join為非靜態方法,定義如下:
void join()
等待該線程終止。
void join(long millis)
等待該線程終止的時間最長為 millis毫秒。
void join(long millis,int nanos)
等待該線程終止的時間最長為 millis毫秒 + nanos 納秒。

public   class Test {
        public    static    void main(String[] args) {
                Thread t1 = new MyThread1();
                t1.start(); 

                for (int i = 0; i < 20; i++) {
                        System.out.println("主線程第" + i +"次執行!");
                        if (i > 2)try { 
                                //t1線程合並到主線程中,主線程停止執行過程,轉而執行t1線程,直到t1執行完畢後繼續。
                                t1.join(); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
} 

class MyThread1 extends Thread { 
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("線程1第" + i + "次執行!");
                } 
        } 
}

Java線程:線程的調度-守護線程
守護線程與普通線程寫法上基本麼啥區別,調用線程對象的方法setDaemon(true),則可以將其設置為守護線程。

守護線程使用的情況較少,但並非無用,舉例來說,JVM的垃圾回收、內存管理等線程都是守護線程。還有就是在做數據庫應用時候,使用的數據庫連接池,連接池本身也包含著很多後台線程,監控連接個數、超時時間、狀態等等。

setDaemon方法的詳細說明:
publicfinalvoid setDaemon(boolean on)將該線程標記為守護線程或用戶線程。當正在運行的線程都是守護線程時,Java虛擬機退出。
該方法必須在啟動線程前調用。

該方法首先調用該線程的 checkAccess方法,且不帶任何參數。這可能拋出 SecurityException(在當前線程中)。

參數:
on - 如果為true,則將該線程標記為守護線程。
拋出:
IllegalThreadStateException - 如果該線程處於活動狀態。
SecurityException - 如果當前線程無法修改該線程。
另請參見:
isDaemon(), checkAccess()

Java線程:線程的同步-同步方法
線程的同步是保證多線程安全訪問競爭資源的一種手段。
線程的同步是Java多線程編程的難點,往往開發者搞不清楚什麼是競爭資源、什麼時候需要考慮同步,怎麼同步等等問題,當然,這些問題沒有很明確的答案,但有些原則問題需要考慮,是否有競爭資源被同時改動的問題?

對於同步,在具體的Java代碼中需要完成一下兩個操作:
把競爭訪問的資源標識為private;
同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。
當然這不是唯一控制並發安全的途徑。

synchronized關鍵字使用說明
synchronized只能標記非抽象的方法,不能標識成員變量。

注意:
通過前文可知,線程退出同步方法時將釋放掉方法所屬對象的鎖,但還應該注意的是,同步方法中還可以使用特定的方法對線程進行調度。這些方法來自於java.lang.Object類。

void notify()
喚醒在此對象監視器上等待的單個線程。
void notifyAll()
喚醒在此對象監視器上等待的所有線程。
void wait()
導致當前的線程等待,直到其他線程調用此對象的 notify()方法或 notifyAll()方法。
void wait(long timeout)
導致當前的線程等待,直到其他線程調用此對象的 notify()方法或 notifyAll()方法,或者超過指定的時間量。
void wait(long timeout,int nanos)
導致當前的線程等待,直到其他線程調用此對象的 notify()方法或 notifyAll()方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量。

Java線程:線程的同步-同步塊
對於同步,除了同步方法外,還可以使用同步代碼塊,有時候同步代碼塊會帶來比同步方法更好的效果。

追其同步的根本的目的,是控制競爭資源的正確的訪問,因此只要在訪問競爭資源的時候保證同一時刻只能一個線程訪問即可,因此Java引入了同步代碼快的策略,以提高性能!
在使用synchronized關鍵字時候,應該盡可能避免在synchronized方法或synchronized塊中使用sleep或者yield方法,因為synchronized程序塊占有著對象鎖,你休息那麼其他的線程只能一邊等著你醒來執行完了才能執行。不但嚴重影響效率,也不合邏輯。
同樣,在同步程序塊內調用yeild方法讓出CPU資源也沒有意義,因為你占用著鎖,其他互斥線程還是無法訪問同步程序塊。當然與同步程序塊無關的線程可以獲得更多的執行時間。
Java線程:並發協作-生產者消費者模型

實際上,准確說應該是“生產者-消費者-倉儲”模型,離開了倉儲,生產者消費者模型就顯得沒有說服力了。
對於此模型,應該明確一下幾點:
1、生產者僅僅在倉儲未滿時候生產,倉滿則停止生產。
2、消費者僅僅在倉儲有產品時候才能消費,倉空則等待。
3、當消費者發現倉儲沒產品可消費時候會通知生產者生產。
4、生產者在生產出可消費產品時候,應該通知等待的消費者去消費。

此模型將要結合java.lang.Object的wait與notify、notifyAll方法來實現以上的需求。這是非常重要的。

對於本例,要說明的是當發現不能滿足生產或者消費條件的時候,調用對象的wait方法,wait方法的作用是釋放當前線程的所獲得的鎖,並調用對象的notifyAll()方法,通知(喚醒)該對象上其他等待線程,使得其繼續執行。這樣,整個生產者、消費者線程得以正確的協作執行。
notifyAll() 方法,起到的是一個通知作用,不釋放鎖,也不獲取鎖。只是告訴該對象上等待的線程“可以競爭執行了,都醒來去執行吧”。

Java線程:並發協作-死鎖
線程發生死鎖可能性很小,即使看似可能發生死鎖的代碼,在運行時發生死鎖的可能性也是小之又小。

發生死鎖的原因一般是兩個對象的鎖相互等待造成的。
Java線程:volatile關鍵字

Java? 語言包含兩種內在的同步機制:同步塊(或方法)和 volatile變量。這兩種機制的提出都是為了實現代碼線程的安全性。其中 Volatile變量的同步性較差(但有時它更簡單並且開銷更低),而且其使用也更容易出錯。

Java線程:新特征-線程池
線程池的基本思想還是一種對象池的思想,開辟一塊內存空間,裡面存放了眾多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣可以避免反復創建線程對象所帶來的性能開銷,節省了系統的資源。

一、固定大小的線程池
二、單任務線程池
三、可變尺寸的線程池
四、延遲連接池
五、單任務延遲連接池
六、自定義線程池

創建自定義線程池的構造方法很多,本例中參數的含義如下:

ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
                         int maximumPoolSize,
                         long keepAliveTime,
                          TimeUnit unit,
                         BlockingQueue workQueue)

用給定的初始參數和默認的線程工廠及處理程序創建新的ThreadPoolExecutor。使用Executors工廠方法之一比使用此通用構造方法方便得多。
參數:
corePoolSize -池中所保存的線程數,包括空閒線程。
maximumPoolSize -池中允許的最大線程數。
keepAliveTime -當線程數大於核心時,此為終止前多余的空閒線程等待新任務的最長時間。
unit - keepAliveTime參數的時間單位。
workQueue -執行前用於保持任務的隊列。此隊列僅保持由execute方法提交的Runnable任務。
拋出:
IllegalArgumentException -如果 corePoolSize或 keepAliveTime小於零,或者 maximumPoolSize小於或等於零,或者 corePoolSize大於 maximumPoolSize。
NullPointerException -如果workQueue為 null

Java線程:新特征-有返回值的線程

可返回值的任務必須實現Callable接口,類似的,無返回值的任務必須Runnable接口。
執行Callable任務後,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。
Java線程:新特征-鎖(上)
在Java5中,專門提供了鎖對象,利用鎖可以方便的實現資源的封鎖,用來控制對競爭資源並發訪問的控制,這些內容主要集中在java.util.concurrent.locks包下面,裡面有三個重要的接口Condition、Lock、ReadWriteLock。

ConditionCondition將Object監視器方法(wait、notify和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意Lock實現組合使用,為每個對象提供多個等待 set(wait-set)。LockLock實現提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作。ReadWriteLockReadWriteLock維護了一對相關的鎖定,一個用於只讀操作,另一個用於寫入操作。

Java線程:新特征-信號量
Java的信號量實際上是一個功能完畢的計數器,對控制一定資源的消費與回收有著很重要的意義,信號量常常用於多線程的代碼中,並能監控有多少數目的線程等待獲取資源,並且通過信號量可以得知可用資源的數目等等,這裡總是在強調“數目”二字,但不能指出來有哪些在等待,哪些資源可用。

下面是一個簡單例子:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

publicclass Test {
        publicstaticvoid main(String[] args) {
                MyPool myPool = new MyPool(20);

                ExecutorService threadPool = Executors.newFixedThreadPool(2); 
                MyThread t1 = new MyThread("任務A", myPool, 3); 
                MyThread t2 = new MyThread("任務B", myPool, 12); 
                MyThread t3 = new MyThread("任務C", myPool, 7); 

                threadPool.execute(t1); 
                threadPool.execute(t2); 
                threadPool.execute(t3); 
                //關閉池
                threadPool.shutdown(); 
        } 
} 

/** 
* 一個池 
*/ 
class MyPool { 
        private Semaphore sp;    //池相關的信號量

        /** 
         * 池的大小,這個大小會傳遞給信號量
         * 
         * @param size 池的大小
         */ 
        MyPool(int size) {
                this.sp =new Semaphore(size);
        } 

        public Semaphore getSp() {
                return sp;
        } 

        publicvoid setSp(Semaphore sp) {
                this.sp = sp;
        } 
} 

class MyThread extends Thread { 
        private String threadname;            //線程的名稱
        private MyPool pool;                        //自定義池
        privateint x;                                    //申請信號量的大小

        MyThread(String threadname, MyPool pool, int x) {
                this.threadname = threadname;
                this.pool = pool;
                this.x = x;
        } 

        publicvoid run() {
                try {
                        //從此信號量獲取給定數目的許可
                        pool.getSp().acquire(x); 
                        //todo:也許這裡可以做更復雜的業務
                        System.out.println(threadname + "成功獲取了" + x +"個許可!");
                } catch (InterruptedException e) {
                        e.printStackTrace(); 
                } finally {
                        //釋放給定數目的許可,將其返回到信號量。
                        pool.getSp().release(x); 
                        System.out.println(threadname + "釋放了" + x +"個許可!");
                } 
        } 
}

任務B成功獲取了12個許可!
任務B釋放了12個許可!
任務A成功獲取了3個許可!
任務C成功獲取了7個許可!
任務C釋放了7個許可!
任務A釋放了3個許可!

從結果可以看出,信號量僅僅是對池資源進行監控,但不保證線程的安全,因此,在使用時候,應該自己控制線程的安全訪問池資源。

Java線程:新特征-阻塞隊列
Java定義了阻塞隊列的接口java.util.concurrent.BlockingQueue,阻塞隊列的概念是,一個指定長度的隊列,如果隊列滿了,添加新元素的操作會被阻塞等待,直到有空位為止。同樣,當隊列為空時候,請求隊列元素的操作同樣會阻塞等待,直到有可用元素為止。

另外,阻塞隊列還有更多實現類,用來滿足各種復雜的需求:ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue,具體的API差別也很小。
Java線程:新特征-阻塞棧
對於阻塞棧,與阻塞隊列相似。不同點在於棧是“後入先出”的結構,每次操作的是棧頂,而隊列是“先進先出”的結構,每次操作的是隊列頭。

Java為阻塞棧定義了接口:java.util.concurrent.BlockingDeque!
Java線程:新特征-條件變
這裡的條件和普通意義上的條件表達式有著天壤之別。

條件變量都實現了java.util.concurrent.locks.Condition接口,條件變量的實例化是通過一個Lock對象上調用newCondition()方法來獲取的,這樣,條件就和一個鎖對象綁定起來了。因此,Java中的條件變量只能和鎖配合使用,來控制並發程序訪問競爭資源的安全。

條件變量的出現是為了更精細控制線程等待與喚醒,在Java5之前,線程的等待與喚醒依靠的是Object對象的wait()和notify()/notifyAll()方法,這樣的處理不夠精細。

而在Java5中,一個鎖可以有多個條件,每個條件上可以有多個線程等待,通過調用await()方法,可以讓線程在該條件下等待。當調用signalAll()方法,又可以喚醒該條件下的等待的線程。有關Condition接口的API可以具體參考JavaAPI文檔。

條件變量比較抽象,原因是他不是自然語言中的條件概念,而是程序控制的一種手段。

Java線程:新特征-原子量
所謂的原子量即操作變量的操作是“原子的”,該操作不可再分,因此是線程安全的。

有關原子量的用法很簡單,關鍵是對原子量的認識,原子僅僅是保證變量操作的原子性,但整個程序還需要考慮線程安全的。
Java線程:新特征-障礙器
Java5中,添加了障礙器類,為了適應一種新的設計需求,比如一個大型的任務,常常需要分配好多子任務去執行,只有當所有子任務都執行完成時候,才能執行主任務,這時候,就可以選擇障礙器了。

障礙器是多線程並發控制的一種手段!

Executors類是什麼?
Executors為Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable類提供了一些工具方法!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved