java線程基礎知識

看到不錯的一篇文章,轉來你們一塊兒進步。html

Java線程:建立與啓動

前端

1、定義線程
 
一、擴展java.lang.Thread類。
 
此類中有個run()方法,應該注意其用法:
public void run()
若是該線程是使用獨立的  Runnable 運行對象構造的,則調用該  Runnable 對象的  run 方法;不然,該方法不執行任何操做並返回。
 
Thread 的子類應該重寫該方法。
二、實現java.lang.Runnable接口。
 
void  run()
使用實現接口  Runnable 的對象建立一個線程時,啓動該線程將致使在獨立執行的線程中調用對象的  run 方法。
 
方法  run 的常規協定是,它可能執行任何所需的操做。
 
2、實例化線程
 
一、若是是擴展java.lang.Thread類的線程,則直接new便可。
 
二、若是是實現了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)
 
3、啓動線程
 
在線程的Thread對象上調用start()方法,而不是run()或者別的方法。
 
在調用start()方法以前:線程處於新狀態中,新狀態指有一個Thread對象,但尚未一個真正的線程。
 
在調用start()方法以後:發生了一系列複雜的事情
啓動新的執行線程(具備新的調用棧);
該線程重新狀態轉移到可運行狀態;
當該線程得到機會執行時,其目標run()方法將運行。
 
注意:對Java來講,run()方法沒有任何特別之處。像main()方法同樣,它只是新線程知道調用的方法名稱(和簽名)。所以,在Runnable上或者Thread上調用run方法是合法的。但並不啓動新的線程。

4、一些常見問題
 
一、線程的名字,一個運行中的線程老是有名字的,名字有兩個來源,一個是虛擬機本身給的名字,一個是你本身的定的名字。在沒有指定線程名字的狀況下,虛擬機總會爲線程指定名字,而且主線程的名字老是mian,非主線程的名字不肯定。
二、線程均可以設置名字,也能夠獲取線程的名字,連主線程也不例外。
三、獲取當前線程的對象的方法是:Thread.currentThread();
四、在上面的代碼中,只能保證:每一個線程都將啓動,每一個線程都將運行直到完成。一系列線程以某種順序啓動並不意味着將按該順序執行。對於任何一組啓動的線程來講,調度程序不能保證其執行次序,持續時間也沒法保證。
五、當線程目標run()方法結束時該線程完成。
六、一旦線程啓動,它就永遠不能再從新啓動。只有一個新的線程能夠被啓動,而且只能一次。一個可運行的線程或死線程能夠被從新啓動。
七、線程的調度是JVM的一部分,在一個CPU的機器上上,實際上一次只能運行一個線程。一次只有一個線程棧執行。JVM線程調度程序決定實際運行哪一個處於可運行狀態的線程。
衆多可運行線程中的某一個會被選中作爲當前線程。可運行線程被選擇運行的順序是沒有保障的。
八、儘管一般採用隊列形式,但這是沒有保障的。隊列形式是指當一個線程完成「一輪」時,它移到可運行隊列的尾部等待,直到它最終排隊到該隊列的 前端爲止,它才能被再次選中。事實上,咱們把它稱爲可運行池而不是一個可運行隊列,目的是幫助認識線程並不都是以某種有保障的順序排列唱呢個一個隊列的事 實。
九、儘管咱們沒有沒法控制線程調度程序,但能夠經過別的方式來影響線程調度的方式。

Java線程:線程棧模型與線程的變量

要理解線程調度的原理,以及線程執行過程,必須理解線程棧模型。
線程棧是指某時刻時內存中線程調度的棧信息,當前調用的方法老是位於棧頂。線程棧的內容是隨着程序的運行動態變化的,所以研究線程棧必須選擇一個運行的時刻(實際上指代碼運行到什麼地方)。
 
下面經過一個示例性的代碼說明線程(調用)棧的變化過程。
 
 
這幅圖描述在代碼執行到兩個不一樣時刻一、2時候,虛擬機線程調用棧示意圖。
 
當程序執行到t.start();時候,程序多出一個分支(增長了一個調用棧B),這樣,棧A、棧B並行執行。
 
從這裏就能夠看出方法調用和線程啓動的區別了。

Java線程:線程狀態的轉換
1、線程狀態
 
線程的狀態轉換是線程控制的基礎。線程狀態總的可分爲五大狀態:分別是生、死、可運行、運行、等待/阻塞。用一個圖來描述以下:
 
一、新狀態:線程對象已經建立,尚未在其上調用start()方法。
 
二、可運行狀態:當線程有資格運行,但調度程序尚未把它選定爲運行線程時線程所處的狀態。當start()方法調用時,線程首先進入可運行狀態。在線程運行以後或者從阻塞、等待或睡眠狀態回來後,也返回到可運行狀態。
 
三、運行狀態:線程調度程序從可運行池中選擇一個線程做爲當前線程時線程所處的狀態。這也是線程進入運行狀態的惟一一種方式。
 
四、等待/阻塞/睡眠狀態:這是線程有資格運行時它所處的狀態。實際上這個三狀態組合爲一種,其共同點是:線程仍舊是活的,可是當前沒有條件運行。換句話說,它是可運行的,可是若是某件事件出現,他可能返回到可運行狀態。
 
五、死亡態:當線程的run()方法完成時就認爲它死去。這個線程對象也許是活的,可是,它已經不是一個單獨執行的線程。線程一旦死亡,就不能 復生。 若是在一個死去的線程上調用start()方法,會拋出java.lang.IllegalThreadStateException異常。
2、阻止線程執行
對於線程的阻止,考慮一下三個方面,不考慮IO阻塞的狀況:
睡眠;
等待;
由於須要一個對象的鎖定而被阻塞。
 
一、睡眠
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)靜態方法強制當前正在執行的線程休眠(暫停執行),以「減慢線程」。當線程睡眠時,它入睡在某個地方,在甦醒以前不會返回到可運行狀態。當睡 眠時間到期,則返回到可運行狀態。
 
線程睡眠的緣由:線程執行太快,或者須要強制進入下一輪,由於Java規範不保證合理的輪換。
 
睡眠的實現:調用靜態方法。
        try {
            Thread.sleep(123);
        } catch (InterruptedException e) {
            e.printStackTrace(); 
        }
 
睡眠的位置:爲了讓其餘線程有機會執行,能夠將Thread.sleep()的調用放線程run()以內。這樣才能保證該線程執行過程當中會睡眠。
注意:
一、線程睡眠是幫助全部線程得到運行機會的最好方法。
二、線程睡眠到期自動甦醒,並返回到可運行狀態,不是運行狀態。sleep()中指定的時間是線程不會運行的最短期。所以,sleep()方法不能保證該線程睡眠到期後就開始執行。
三、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
          分配給線程的默認優先級。
 
三、Thread.yield()方法
 
Thread.yield()方法做用是:暫停當前正在執行的線程對象,並執行其餘線程。
yield()應該作的是讓當前運行線程回到可運行狀態,以容許具備相同優先級的其餘線程得到運行機會。所以,使用yield()的目的是讓相 同優先級的線程之間能適當的輪轉執行。可是,實際中沒法保證yield()達到讓步目的,由於讓步的線程還有可能被線程調度程序再次選中。
結論:yield()從未致使線程轉到等待/睡眠/阻塞狀態。在大多數狀況下,yield()將致使線程從運行狀態轉到可運行狀態,但有可能沒有效果。
 
四、join()方法
 
Thread的非靜態方法join()讓一個線程B「加入」到另一個線程A的尾部。在A執行完畢以前,B不能工做。例如:
        Thread t = new MyThread();
        t.start();
        t.join();
另外,join()方法還有帶超時限制的重載版本。 例如t.join(5000);則讓線程等待5000毫秒,若是超過這個時間,則中止等待,變爲可運行狀態。
 
線程的加入join()對線程棧致使的結果是線程棧發生了變化,固然這些變化都是瞬時的。下面給示意圖:
 
 
 
小結
到目前位置,介紹了線程離開運行狀態的3種方法:
一、調用Thread.sleep():使當前線程睡眠至少多少毫秒(儘管它可能在指定的時間以前被中斷)。
二、調用Thread.yield():不能保障太多事情,儘管一般它會讓當前運行線程回到可運行性狀態,使得有相同優先級的線程有機會執行。
三、調用join()方法:保證當前線程中止執行,直到該線程所加入的線程完成爲止。然而,若是它加入的線程沒有存活,則當前線程不須要中止。
 
除了以上三種方式外,還有下面幾種特殊狀況可能使線程離開運行狀態:
一、線程的run()方法完成。
二、在對象上調用wait()方法(不是在線程上調用)。
三、線程不能在對象上得到鎖定,它正試圖運行該對象的方法代碼。
四、線程調度程序能夠決定將當前運行狀態移動到可運行狀態,以便讓另外一個線程得到運行機會,而不須要任何理由。

Java線程:線程的同步與鎖


1、同步問題提出
 
線程的同步是爲了防止多個線程訪問一個數據對象時,對數據形成的破壞。
例如:兩個線程ThreadA、ThreadB都操做同一個對象Foo對象,並修改Foo對象上的數據。
 
public  class Foo {
     private  int x = 100;

     public  int getX() {
         return x;
    }

     public  int fix( int y) {
        x = x - y;
         return x;
    }
}
 
public  class MyRunnable  implements Runnable {
     private Foo foo =  new Foo();

     public  static  void main(String[] args) {
        MyRunnable r =  new MyRunnable();
        Thread ta =  new Thread(r,  "Thread-A");
        Thread tb =  new Thread(r,  "Thread-B");
        ta.start();
        tb.start();
    }

     public  void run() {
         for ( int i = 0; i < 3; i++) {
             this.fix(30);
             try {
                Thread.sleep(1);
            }  catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +  " : 當前foo對象的x值= " + foo.getX());
        }
    }

     public  int fix( int y) {
         return foo.fix(y);
    }
}
 
運行結果:
Thread-A : 當前foo對象的x值= 40
Thread-B : 當前foo對象的x值= 40
Thread-B : 當前foo對象的x值= -20
Thread-A : 當前foo對象的x值= -50
Thread-A : 當前foo對象的x值= -80
Thread-B : 當前foo對象的x值= -80

Process finished with exit code 0
 
從結果發現,這樣的輸出值明顯是不合理的。緣由是兩個線程不加控制的訪問Foo對象並修改其數據所致。
 
若是要保持結果的合理性,只須要達到一個目的,就是將對Foo的訪問加以限制,每次只能有一個線程在訪問。這樣就能保證Foo對象中數據的合理性了。
 
在具體的Java代碼中須要完成一下兩個操做:
把競爭訪問的資源類Foo變量x標識爲private;
同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。
 
2、同步和鎖定
 
一、鎖的原理
 
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;
        }
    }
效果是徹底同樣的。
 
3、靜態方法同步
 
要同步靜態方法,須要一個用於整個類對象的鎖,這個對象是就是這個類(XXX.class)。
例如:
public static synchronized int setName(String name){
      Xxx.name = name;
}
等價於
public static int setName(String name){
      synchronized(Xxx.class){
            Xxx.name = name;
      }
}

 
4、若是線程不能不能得到鎖會怎麼樣
 
若是線程試圖進入同步方法,而其鎖已經被佔用,則線程在該對象上被阻塞。實質上,線程進入該對象的的一種池中,必須在哪裏等待,直到其鎖被釋放,該線程再次變爲可運行或運行爲止。
 
當考慮阻塞時,必定要注意哪一個對象正被用於鎖定:
一、調用同一個對象中非靜態同步方法的線程將彼此阻塞。若是是不一樣對象,則每一個線程有本身的對象的鎖,線程間彼此互不干預。
 
二、調用同一個類中的靜態同步方法的線程將彼此阻塞,它們都是鎖定在相同的Class對象上。
 
三、靜態同步方法和非靜態同步方法將永遠不會彼此阻塞,由於靜態方法鎖定在Class對象上,非靜態方法鎖定在該類的對象上。
 
四、對於同步代碼塊,要看清楚什麼對象已經用於鎖定(synchronized後面括號的內容)。在同一個對象上進行同步的線程將彼此阻塞,在不一樣對象上鎖定的線程將永遠不會彼此阻塞。
 
5、什麼時候須要同步
 
在多個線程同時訪問互斥(可交換)數據時,應該同步以保護數據,確保兩個線程不會同時修改更改它。
 
對於非靜態字段中可更改的數據,一般使用非靜態方法訪問。
對於靜態字段中可更改的數據,一般使用靜態方法訪問。
 
若是須要在非靜態方法中使用靜態字段,或者在靜態字段中調用非靜態方法,問題將變得很是複雜。已經超出SJCP考試範圍了。
 
6、線程安全類
 
當一個類已經很好的同步以保護它的數據時,這個類就稱爲「線程安全的」。
 
即便是線程安全類,也應該特別當心,由於操做的線程是間仍然不必定安全。
 
舉個形象的例子,好比一個集合是線程安全的,有兩個線程在操做同一個集合對象,當第一個線程查詢集合非空後,刪除集合中全部元素的時候。第二個 線程也來執行與第一個線程相同的操做,也許在第一個線程查詢後,第二個線程也查詢出集合非空,可是當第一個執行清除後,第二個再執行刪除顯然是不對的,因 爲此時集合已經爲空了。
看個代碼:
 
public  class NameList {
     private List nameList = Collections.synchronizedList( new LinkedList());

     public  void add(String name) {
        nameList.add(name);
    }

     public String removeFirst() {
         if (nameList.size() > 0) {
             return (String) nameList.remove(0);
        }  else {
             return  null;
        }
    }
}
 
public  class Test {
     public  static  void main(String[] args) {
         final NameList nl =  new NameList();
        nl.add( "aaa");
         class NameDropper  extends Thread{
             public  void run(){
                String name = nl.removeFirst();
                System.out.println(name);
            }
        }

        Thread t1 =  new NameDropper();
        Thread t2 =  new NameDropper();
        t1.start();
        t2.start();
    }
}
 
雖然集合對象
    private List nameList = Collections.synchronizedList(new LinkedList());
是同步的,可是程序還不是線程安全的。
出現這種事件的緣由是,上例中一個線程操做列表過程當中沒法阻止另一個線程對列表的其餘操做。
 
解決上面問題的辦法是,在操做集合對象的NameList上面作一個同步。改寫後的代碼以下:
public  class NameList {
     private List nameList = Collections.synchronizedList( new LinkedList());

     public  synchronized  void add(String name) {
        nameList.add(name);
    }

     public  synchronized String removeFirst() {
         if (nameList.size() > 0) {
             return (String) nameList.remove(0);
        }  else {
             return  null;
        }
    }
}
 
這樣,當一個線程訪問其中一個同步方法時,其餘線程只有等待。
 
7、線程死鎖
 
死鎖對Java程序來講,是很複雜的,也很難發現問題。當兩個線程被阻塞,每一個線程在等待另外一個線程時就發生死鎖。
 
仍是看一個比較直觀的死鎖例子:
 
public  class DeadlockRisk {
     private  static  class Resource {
         public  int value;
    }

     private Resource resourceA =  new Resource();
     private Resource resourceB =  new Resource();

     public  int read() {
         synchronized (resourceA) {
             synchronized (resourceB) {
                 return resourceB.value + resourceA.value;
            }
        }
    }

     public  void write( int a,  int b) {
         synchronized (resourceB) {
             synchronized (resourceA) {
                resourceA.value = a;
                resourceB.value = b;
            }
        }
    }
}
 
假設read()方法由一個線程啓動,write()方法由另一個線程啓動。讀線程將擁有resourceA鎖,寫線程將擁有resourceB鎖,二者都堅持等待的話就出現死鎖。
 
實際上,上面這個例子發生死鎖的機率很小。由於在代碼內的某個點,CPU必須從讀線程切換到寫線程,因此,死鎖基本上不能發生。
 
可是,不管代碼中發生死鎖的機率有多小,一旦發生死鎖,程序就死掉。有一些設計方法能幫助避免死鎖,包括始終按照預約義的順序獲取鎖這一策略。已經超出SCJP的考試範圍。
 
8、線程同步小結
 
一、線程同步的目的是爲了保護多個線程反問一個資源時對資源的破壞。
二、線程同步方法是經過鎖來實現,每一個對象都有切僅有一個鎖,這個鎖與一個特定的對象關聯,線程一旦獲取了對象鎖,其餘訪問該對象的線程就沒法再訪問該對象的其餘同步方法。
三、對於靜態同步方法,鎖是針對這個類的,鎖對象是該類的Class對象。靜態和非靜態方法的鎖互不干預。一個線程得到鎖,當在一個同步方法中訪問另外對象上的同步方法時,會獲取這兩個對象鎖。
四、對於同步,要時刻清醒在哪一個對象上同步,這是關鍵。
五、編寫線程安全的類,須要時刻注意對多個線程競爭訪問資源的邏輯和安全作出正確的判斷,對「原子」操做作出分析,並保證原子操做期間別的線程沒法訪問競爭資源。
六、當多個線程等待一個對象鎖時,沒有獲取到鎖的線程將發生阻塞。
七、死鎖是線程間相互等待鎖鎖形成的,在實際中發生的機率很是的小。真讓你寫個死鎖程序,不必定好使,呵呵。可是,一旦程序發生死鎖,程序將死掉。

Java線程:線程的交互

線程交互是比較複雜的問題,SCJP要求不很基礎:給定一個場景,編寫代碼來恰當使用等待、通知和通知全部線程。
 
1、線程交互的基礎知識
 
SCJP所要求的線程交互知識點須要從 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( "等待對象b完成計算。。。");
                 //當前線程A等待
                b.wait();
            }  catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println( "b對象計算的總和是:" + b.total);
        }
    }
}
 

public  class ThreadB  extends Thread {
     int total;

     public  void run() {
         synchronized ( this) {
             for ( int i = 0; i < 101; i++) {
                total += i;
            }
             //(完成計算了)喚醒在此對象監視器上等待的單個線程,在本例中線程A被喚醒
            notify();
        }
    }
}
 
等待對象b完成計算。。。
b對象計算的總和是:5050

Process finished with exit code 0
 
千萬注意:
當在對象上調用wait()方法時,執行該代碼的線程當即放棄它在對象上的鎖。然而調用notify()時,並不意味着這時線程會放棄其鎖。若是線程榮然在完成同步代碼,則線程在移出以前不會放棄鎖。所以,只要調用notify()並不意味着這時該鎖變得可用。
 
2、多個線程在等待一個對象鎖時候使用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;
        }

         public  void run() {
                 synchronized (c) {
                         try {
                                System.out.println(Thread.currentThread() +  "等待計算結果。。。");
                                c.wait();
                        }  catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread() +  "計算結果爲:" + c.total);
                }
        }

         public  static  void main(String[] args) {
                Calculator calculator =  new Calculator();

                 //啓動三個線程,分別獲取計算結果
                 new ReaderResult(calculator).start();
                 new ReaderResult(calculator).start();
                 new ReaderResult(calculator).start();
                 //啓動計算線程
                calculator.start();
        }
}
 
運行結果:
Thread[Thread-1,5,main]等待計算結果。。。
Thread[Thread-2,5,main]等待計算結果。。。
Thread[Thread-3,5,main]等待計算結果。。。
Exception in thread  "Thread-0" java.lang.IllegalMonitorStateException: current thread not owner
  at java.lang.Object.notifyAll(Native Method)
  at threadtest.Calculator.run(Calculator.java:18)
Thread[Thread-1,5,main]計算結果爲:5050
Thread[Thread-2,5,main]計算結果爲:5050
Thread[Thread-3,5,main]計算結果爲:5050

Process finished with exit code 0
 
運行結果代表,程序中有異常,而且屢次運行結果可能有多種輸出結果。這就是說明,這個多線程的交互程序還存在問題。到底是出了什麼問題,須要深刻的分析和思考,下面將作具體分析。
 
實際上,上面這個代碼中,咱們指望的是讀取結果的線程在計算線程調用notifyAll()以前等待便可。 可是,若是計算線程先執行,並在讀取結果線程等待以前調用了notify()方法,那麼又會發生什麼呢?這種狀況是可能發生的。由於沒法保證線程的不一樣部 分將按照什麼順序來執行。幸運的是當讀取線程運行時,它只能立刻進入等待狀態----它沒有作任何事情來檢查等待的事件是否已經發生。  ----所以,若是計算線程已經調用了notifyAll()方法,那麼它就不會再次調用notifyAll(),----而且等待的讀取線程將永遠保持 等待。這固然是開發者所不肯意看到的問題。
 
所以,當等待的事件發生時,須要可以檢查notifyAll()通知事件是否已經發生。


Java線程:線程的調度-休眠
 
Java線程調度是Java多線程的核心,只有良好的調度,才能充分發揮系統的性能,提升程序的執行效率。
 
這裏要明確的一點,無論程序員怎麼編寫調度,只能最大限度的影響線程執行的次序,而不能作到精準控制。
 
線程休眠的目的是使線程讓出CPU的最簡單的作法之一,線程休眠時候,會將CPU資源交給其餘線程,以便能輪換執行,當休眠必定時間後,線程會甦醒,進入準備狀態等待執行。
 
線程休眠的方法是Thread.sleep(long millis) 和Thread.sleep(long millis, int nanos) ,均爲靜態方法,那調用sleep休眠的哪一個線程呢?簡單說,哪一個線程調用sleep,就休眠哪一個線程。

Java線程:線程的調度-優先級

Java線程:線程的調度-讓步
線程的讓步含義就是使當前運行着線程讓出CPU資源,可是然給誰不知道,僅僅是讓出,線程狀態回到可運行狀態。
 
線程的讓步使用Thread.yield()方法,yield() 爲靜態方法,功能是暫停當前正在執行的線程對象,並執行其餘線程。
Java線程:線程的調度-合併
線程的合併的含義就是將幾個並行線程的線程合併爲一個單線程執行,應用場景是當一個線程必須等待另外一個線程執行完畢才能執行時可使用join方法。
 
join爲非靜態方法,定義以下:
void join()    
    等待該線程終止。    
void join( long millis)    
    等待該線程終止的時間最長爲 millis 毫秒。    
void join( long millis,  int nanos)    
    等待該線程終止的時間最長爲 millis 毫秒 + nanos 納秒。


Java線程:線程的調度-守護線程
守護線程與普通線程寫法上基本麼啥區別,調用線程對象的方法setDaemon(true),則能夠將其設置爲守護線程。
 
守護線程使用的狀況較少,但並不是無用,舉例來講,JVM的垃圾回收、內存管理等線程都是守護線程。還有就是在作數據庫應用時候,使用的數據庫鏈接池,鏈接池自己也包含着不少後臺線程,監控鏈接個數、超時時間、狀態等等。
 
setDaemon方法的詳細說明:
public  final  void setDaemon( boolean on)將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。    
  該方法必須在啓動線程前調用。    

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


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

 

public  class Test {
         public  static  void main(String[] args) {
                Thread t1 =  new MyCommon();
                Thread t2 =  new Thread( new MyDaemon());
                t2.setDaemon( true);         //設置爲守護線程

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

class MyCommon  extends Thread {
         public  void run() {
                 for ( int i = 0; i < 5; i++) {
                        System.out.println( "線程1第" + i +  "次執行!");
                         try {
                                Thread.sleep(7);
                        }  catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                }
        }
}

class MyDaemon  implements Runnable {
         public  void run() {
                 for ( long i = 0; i < 9999999L; i++) {
                        System.out.println( "後臺線程第" + i +  "次執行!");
                         try {
                                Thread.sleep(7);
                        }  catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                }
        }
}
 
後臺線程第0次執行!
線程1第0次執行!
線程1第1次執行!
後臺線程第1次執行!
後臺線程第2次執行!
線程1第2次執行!
線程1第3次執行!
後臺線程第3次執行!
線程1第4次執行!
後臺線程第4次執行!
後臺線程第5次執行!
後臺線程第6次執行!
後臺線程第7次執行!

Process finished with exit code 0
 
從上面的執行結果能夠看出:
前臺線程是保證執行完畢的,後臺線程尚未執行完畢就退出了。
 
實際上:JRE判斷程序是否執行結束的標準是全部的前臺執線程行完畢了,而無論後臺線程的狀態,所以,在使用後臺進程時候必定要注意這個問題。

Java線程:線程的同步-同步方法
線程的同步是保證多線程安全訪問競爭資源的一種手段。
線程的同步是Java多線程編程的難點,每每開發者搞不清楚什麼是競爭資源、何時須要考慮同步,怎麼同步等等問題,固然,這些問題沒有很明確的答案,但有些原則問題須要考慮,是否有競爭資源被同時改動的問題?
 
在本文以前,請參閱《 Java線程:線程的同步與鎖》,本文是在此基礎上所寫的。
 
對於同步,在具體的Java代碼中須要完成一下兩個操做:
把競爭訪問的資源標識爲private;
同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。
固然這不是惟一控制併發安全的途徑。
 
synchronized關鍵字使用說明
synchronized只能標記非抽象的方法,不能標識成員變量。
 
爲了演示同步方法的使用,構建了一個信用卡帳戶,起初信用額爲100w,而後模擬透支、存款等多個操做。顯然銀行帳戶User對象是個競爭資源,而多個併發操做的是帳戶方法oper(int x),固然應該在此方法上加上同步,並將帳戶的餘額設爲私有變量,禁止直接訪問。
 
 

public  class Test {
         public  static  void main(String[] args) {
                User u =  new User( "張三", 100);
                MyThread t1 =  new MyThread( "線程A", u, 20);
                MyThread t2 =  new MyThread( "線程B", u, -60);
                MyThread t3 =  new MyThread( "線程C", u, -80);
                MyThread t4 =  new MyThread( "線程D", u, -30);
                MyThread t5 =  new MyThread( "線程E", u, 32);
                MyThread t6 =  new MyThread( "線程F", u, 21);

                t1.start();
                t2.start();
                t3.start();
                t4.start();
                t5.start();
                t6.start();
        }
}

class MyThread  extends Thread {
         private User u;
         private  int y = 0;

        MyThread(String name, User u,  int y) {
                 super(name);
                 this.u = u;
                 this.y = y;
        }

         public  void run() {
                u.oper(y);
        }
}

class User {
         private String code;
         private  int cash;

        User(String code,  int cash) {
                 this.code = code;
                 this.cash = cash;
        }

         public String getCode() {
                 return code;
        }

         public  void setCode(String code) {
                 this.code = code;
        }

        
         public  synchronized  void oper( int x) {
                 try {
                        Thread.sleep(10L);
                         this.cash += x;
                        System.out.println(Thread.currentThread().getName() +  "運行結束,增長「" + x +  "」,當前用戶帳戶餘額爲:" + cash);
                        Thread.sleep(10L);
                }  catch (InterruptedException e) {
                        e.printStackTrace();
                }
        }

        @Override
         public String toString() {
                 return  "User{" +
                                 "code='" + code + '\'' +
                                 ", cash=" + cash +
                                '}';
        }
}
 
輸出結果:
線程A運行結束,增長「20」,當前用戶帳戶餘額爲:120
線程F運行結束,增長「21」,當前用戶帳戶餘額爲:141
線程E運行結束,增長「32」,當前用戶帳戶餘額爲:173
線程C運行結束,增長「-80」,當前用戶帳戶餘額爲:93
線程B運行結束,增長「-60」,當前用戶帳戶餘額爲:33
線程D運行結束,增長「-30」,當前用戶帳戶餘額爲:3

Process finished with exit code 0
 
 
反面教材,不一樣步的狀況,也就是去掉oper(int x)方法的synchronized修飾符,而後運行程序,結果以下:
線程A運行結束,增長「20」,當前用戶帳戶餘額爲:61
線程D運行結束,增長「-30」,當前用戶帳戶餘額爲:63
線程B運行結束,增長「-60」,當前用戶帳戶餘額爲:3
線程F運行結束,增長「21」,當前用戶帳戶餘額爲:61
線程E運行結束,增長「32」,當前用戶帳戶餘額爲:93
線程C運行結束,增長「-80」,當前用戶帳戶餘額爲:61

Process finished with exit code 0
 
很顯然,上面的結果是錯誤的,致使錯誤的緣由是多個線程併發訪問了競爭資源u,並對u的屬性作了改動。
 
可見同步的重要性。
 
 
注意:
經過前文可知,線程退出同步方法時將釋放掉方法所屬對象的鎖,但還應該注意的是,同步方法中還可使用特定的方法對線程進行調度。這些方法來自於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引入了同步代碼快的策略,以提升性能。
 
在上個例子的基礎上,對oper方法作了改動,由同步方法改成同步代碼塊模式,程序的執行邏輯並無問題。
 
 

public  class Test {
         public  static  void main(String[] args) {
                User u =  new User( "張三", 100);
                MyThread t1 =  new MyThread( "線程A", u, 20);
                MyThread t2 =  new MyThread( "線程B", u, -60);
                MyThread t3 =  new MyThread( "線程C", u, -80);
                MyThread t4 =  new MyThread( "線程D", u, -30);
                MyThread t5 =  new MyThread( "線程E", u, 32);
                MyThread t6 =  new MyThread( "線程F", u, 21);

                t1.start();
                t2.start();
                t3.start();
                t4.start();
                t5.start();
                t6.start();
        }
}

class MyThread  extends Thread {
         private User u;
         private  int y = 0;

        MyThread(String name, User u,  int y) {
                 super(name);
                 this.u = u;
                 this.y = y;
        }

         public  void run() {
                u.oper(y);
        }
}

class User {
         private String code;
         private  int cash;

        User(String code,  int cash) {
                 this.code = code;
                 this.cash = cash;
        }

         public String getCode() {
                 return code;
        }

         public  void setCode(String code) {
                 this.code = code;
        }

        
         public  void oper( int x) {
                 try {
                        Thread.sleep(10L);
                         synchronized ( this) {
                                 this.cash += x;
                                System.out.println(Thread.currentThread().getName() +  "運行結束,增長「" + x +  "」,當前用戶帳戶餘額爲:" + cash);
                        }
                        Thread.sleep(10L);
                }  catch (InterruptedException e) {
                        e.printStackTrace();
                }
        }

        @Override
         public String toString() {
                 return  "User{" +
                                 "code='" + code + '\'' +
                                 ", cash=" + cash +
                                '}';
        }
}
 
線程E運行結束,增長「32」,當前用戶帳戶餘額爲:132
線程B運行結束,增長「-60」,當前用戶帳戶餘額爲:72
線程D運行結束,增長「-30」,當前用戶帳戶餘額爲:42
線程F運行結束,增長「21」,當前用戶帳戶餘額爲:63
線程C運行結束,增長「-80」,當前用戶帳戶餘額爲:-17
線程A運行結束,增長「20」,當前用戶帳戶餘額爲:3

Process finished with exit code 0
 
注意:
在使用synchronized關鍵字時候,應該儘量避免在synchronized方法或synchronized塊中使用sleep或者 yield方法,由於synchronized程序塊佔有着對象鎖,你休息那麼其餘的線程只能一邊等着你醒來執行完了才能執行。不但嚴重影響效率,也不合 邏輯。
一樣,在同步程序塊內調用yeild方法讓出CPU資源也沒有意義,由於你佔用着鎖,其餘互斥線程仍是沒法訪問同步程序塊。固然與同步程序塊無關的線程能夠得到更多的執行時間。


Java線程:併發協做-生產者消費者模型
對於多線程程序來講,無論任何編程語言,生產者和消費者模型都是最經典的。就像學習每一門編程語言同樣,Hello World!都是最經典的例子。
 
實際上,準確說應該是「生產者-消費者-倉儲」模型,離開了倉儲,生產者消費者模型就顯得沒有說服力了。
對於此模型,應該明確一下幾點:
一、生產者僅僅在倉儲未滿時候生產,倉滿則中止生產。
二、消費者僅僅在倉儲有產品時候才能消費,倉空則等待。
三、當消費者發現倉儲沒產品可消費時候會通知生產者生產。
四、生產者在生產出可消費產品時候,應該通知等待的消費者去消費。
 
此模型將要結合java.lang.Object的wait與notify、notifyAll方法來實現以上的需求。這是很是重要的。


Java線程 :併發協做-死鎖
線程發生死鎖可能性很小,即便看似可能發生死鎖的代碼,在運行時發生死鎖的可能性也是小之又小。
 
發生死鎖的緣由通常是兩個對象的鎖相互等待形成的。
 
在《Java線程:線程的同步與鎖》一文中,簡述死鎖的概念與簡單例子,可是所給的例子是不完整的,這裏給出一個完整的例子。
 

public  class Test {
         public  static  void main(String[] args) {
                DeadlockRisk dead =  new DeadlockRisk();
                MyThread t1 =  new MyThread(dead, 1, 2);
                MyThread t2 =  new MyThread(dead, 3, 4);
                MyThread t3 =  new MyThread(dead, 5, 6);
                MyThread t4 =  new MyThread(dead, 7, 8);

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

}

class MyThread  extends Thread {
         private DeadlockRisk dead;
         private  int a, b;


        MyThread(DeadlockRisk dead,  int a,  int b) {
                 this.dead = dead;
                 this.a = a;
                 this.b = b;
        }

        @Override
         public  void run() {
                dead.read();
                dead.write(a, b);
        }
}

class DeadlockRisk {
         private  static  class Resource {
                 public  int value;
        }

         private Resource resourceA =  new Resource();
         private Resource resourceB =  new Resource();

         public  int read() {
                 synchronized (resourceA) {
                        System.out.println( "read():" + Thread.currentThread().getName() +  "獲取了resourceA的鎖!");
                         synchronized (resourceB) {
                                System.out.println( "read():" + Thread.currentThread().getName() +  "獲取了resourceB的鎖!");
                                 return resourceB.value + resourceA.value;
                        }
                }
        }

         public  void write( int a,  int b) {
                 synchronized (resourceB) {
                        System.out.println( "write():" + Thread.currentThread().getName() +  "獲取了resourceA的鎖!");
                         synchronized (resourceA) {
                                System.out.println( "write():" + Thread.currentThread().getName() +  "獲取了resourceB的鎖!");
                                resourceA.value = a;
                                resourceB.value = b;
                        }
                }
        }
}
 
下面死鎖的狀況發生了,真是可貴一見啊:
 
Java線程:volatile關鍵字
Java™ 語言包含兩種內在的同步機制:同步塊(或方法)和 volatile 變量。這兩種機制的提出都是爲了實現代碼線程的安全性。其中 Volatile 變量的同步性較差(但有時它更簡單而且開銷更低),並且其使用也更容易出錯。
 
談及到volatile關鍵字,不得不提的一篇文章是:《 Java 理論與實踐: 正確使用 Volatile 變量》,這篇文章對volatile關鍵字的用法作了至關精闢的闡述。
 
之因此要單獨提出volatile這個不經常使用的關鍵字緣由是這個關鍵字在高性能的多線程程序中也有很重要的用途,只是這個關鍵字用很差會出不少問題。
 
首先考慮一個問題,爲何變量須要volatile來修飾呢?
要搞清楚這個問題,首先應該明白計算機內部都作什麼了。好比作了一個i++操做,計算機內部作了三次處理:讀取-修改-寫入。
一樣,對於一個long型數據,作了個賦值操做,在32系統下須要通過兩步才能完成,先修改低32位,而後修改高32位。
 
假想一下,當將以上的操做放到一個多線程環境下操做時候,有可能出現的問題,是這些步驟執行了一部分,而另一個線程就已經引用了變量值,這樣就致使了讀取髒數據的問題。
 
經過這個設想,就不難理解volatile關鍵字了。
 
volatile能夠用在任何變量前面,但不能用於final變量前面,由於final型的變量是禁止修改的。也不存在線程安全的問題。
 
更多的內容,請參看::《 Java 理論與實踐: 正確使用 Volatile 變量》一文,寫得很好。

Java線程:新特徵-線程池
Sun在Java5中,對Java線程的類庫作了大量的擴展,其中線程池就是Java5的新特徵之一,除了線程池以外,還有不少多線程相關的內容,爲多線程的編程帶來了極大便利。爲了編寫高效穩定可靠的多線程程序,線程部分的新增內容顯得尤其重要。
 
有關Java5線程新特徵的內容所有在java.util.concurrent下面,裏面包含數目衆多的接口和類,熟悉這部分API特徵是一項艱難的學習過程。目前有關這方面的資料和書籍都少之又少,大所屬介紹線程方面書籍還停留在java5以前的知識層面上。
 
固然新特徵對作多線程程序沒有必須的關係,在java5以前通用能夠寫出很優秀的多線程程序。只是代價不同而已。
 
線程池的基本思想仍是一種對象池的思想,開闢一塊內存空間,裏面存放了衆多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣能夠避免反覆建立線程對象所帶來的性能開銷,節省了系統的資源。
 
在Java5以前,要實現一個線程池是至關有難度的,如今Java5爲咱們作好了一切,咱們只須要按照提供的API來使用,便可享受線程池帶來的極大便利。
 
Java5的線程池分好多種:固定尺寸的線程池、可變尺寸鏈接池、。
 
在使用線程池以前,必須知道如何去建立一個線程池,在Java5中,須要瞭解的是java.util.concurrent.Executors類的API,這個類提供大量建立鏈接池的靜態方法,是必須掌握的。
 
1、固定大小的線程池
 
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;


public  class Test {
         public  static  void main(String[] args) {
                 //建立一個可重用固定線程數的線程池
                ExecutorService pool = Executors.newFixedThreadPool(2);
                 //建立實現了Runnable接口對象,Thread對象固然也實現了Runnable接口
                Thread t1 =  new MyThread();
                Thread t2 =  new MyThread();
                Thread t3 =  new MyThread();
                Thread t4 =  new MyThread();
                Thread t5 =  new MyThread();
                 //將線程放入池中進行執行
                pool.execute(t1);
                pool.execute(t2);
                pool.execute(t3);
                pool.execute(t4);
                pool.execute(t5);
                 //關閉線程池
                pool.shutdown();
        }
}

class MyThread  extends Thread{
        @Override
         public  void run() {
                System.out.println(Thread.currentThread().getName()+ "正在執行。。。");
        }
}
 
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。

Process finished with exit code 0
 
2、單任務線程池
 
在上例的基礎上改一行建立pool對象的代碼爲:
                 //建立一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。
                ExecutorService pool = Executors.newSingleThreadExecutor();
 
輸出結果爲:
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。

Process finished with exit code 0
 
對於以上兩種鏈接池,大小都是固定的,當要加入的池的線程(或者任務)超過池最大尺寸時候,則入此線程池須要排隊等待。
一旦池中有線程完畢,則排隊等待的某個線程會入池執行。
 
3、可變尺寸的線程池
 
與上面的相似,只是改動下pool的建立方式:
                 //建立一個可根據須要建立新線程的線程池,可是在之前構造的線程可用時將重用它們。
                ExecutorService pool = Executors.newCachedThreadPool();
 
pool-1-thread-5正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-4正在執行。。。
pool-1-thread-3正在執行。。。
pool-1-thread-2正在執行。。。

Process finished with exit code 0
 
4、延遲鏈接池
 
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


public  class Test {
         public  static  void main(String[] args) {
                 //建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。
                ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
                 //建立實現了Runnable接口對象,Thread對象固然也實現了Runnable接口
                Thread t1 =  new MyThread();
                Thread t2 =  new MyThread();
                Thread t3 =  new MyThread();
                Thread t4 =  new MyThread();
                Thread t5 =  new MyThread();
                 //將線程放入池中進行執行
                pool.execute(t1);
                pool.execute(t2);
                pool.execute(t3);
                 //使用延遲執行風格的方法
                pool.schedule(t4, 10, TimeUnit.MILLISECONDS);
                pool.schedule(t5, 10, TimeUnit.MILLISECONDS);
                 //關閉線程池
                pool.shutdown();
        }
}

class MyThread  extends Thread {
        @Override
         public  void run() {
                System.out.println(Thread.currentThread().getName() +  "正在執行。。。");
        }
}
 
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。

Process finished with exit code 0
 
5、單任務延遲鏈接池
 
在四代碼基礎上,作改動
                 //建立一個單線程執行程序,它可安排在給定延遲後運行命令或者按期地執行。
                ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
 
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。

Process finished with exit code 0
 
6、自定義線程池
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public  class Test {
         public  static  void main(String[] args) {
                 //建立等待隊列
                BlockingQueue<Runnable> bqueue =  new ArrayBlockingQueue<Runnable>(20);
                 //建立一個單線程執行程序,它可安排在給定延遲後運行命令或者按期地執行。
                ThreadPoolExecutor pool =  new ThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue);
                 //建立實現了Runnable接口對象,Thread對象固然也實現了Runnable接口
                Thread t1 =  new MyThread();
                Thread t2 =  new MyThread();
                Thread t3 =  new MyThread();
                Thread t4 =  new MyThread();
                Thread t5 =  new MyThread();
                Thread t6 =  new MyThread();
                Thread t7 =  new MyThread();
                 //將線程放入池中進行執行
                pool.execute(t1);
                pool.execute(t2);
                pool.execute(t3);
                pool.execute(t4);
                pool.execute(t5);
                pool.execute(t6);
                pool.execute(t7);
                 //關閉線程池
                pool.shutdown();
        }
}

class MyThread  extends Thread {
        @Override
         public  void run() {
                System.out.println(Thread.currentThread().getName() +  "正在執行。。。");
                 try {
                        Thread.sleep(100L);
                }  catch (InterruptedException e) {
                        e.printStackTrace();
                }
        }
}
 
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。

Process finished with exit code 0
 
建立自定義線程池的構造方法不少,本例中參數的含義以下:

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
用給定的初始參數和默認的線程工廠及處理程序建立新的  ThreadPoolExecutor。使用  Executors 工廠方法之一比使用此通用構造方法方便得多。
參數:
corePoolSize - 池中所保存的線程數,包括空閒線程。
maximumPoolSize - 池中容許的最大線程數。
keepAliveTime - 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。
unit - keepAliveTime 參數的時間單位。
workQueue - 執行前用於保持任務的隊列。此隊列僅保持由  execute 方法提交的  Runnable 任務。
拋出:
IllegalArgumentException - 若是 corePoolSize 或 keepAliveTime 小於零,或者 maximumPoolSize 小於或等於零,或者 corePoolSize 大於 maximumPoolSize。
NullPointerException - 若是  workQueue 爲 null
 
自定義鏈接池稍微麻煩些,不過經過建立的ThreadPoolExecutor線程池對象,能夠獲取到當前線程池的尺寸、正在執行任務的線程數、工做隊列等等。
 
有關Java5線程池的內容到此就沒有了,更多的內容還須要研讀API來獲取。

Java線程:新特徵-有返回值的線程
在Java5以前,線程是沒有返回值的,經常爲了「有」返回值,破費周折,並且代碼很很差寫。或者乾脆繞過這道坎,走別的路了。
 
如今Java終於有可返回值的任務(也能夠叫作線程)了。
 
可返回值的任務必須實現Callable接口,相似的,無返回值的任務必須Runnable接口。
 
執行Callable任務後,能夠獲取一個Future的對象,在該對象上調用get就能夠獲取到Callable任務返回的Object了。
 
下面是個很簡單的例子:
 
import java.util.concurrent.*;


public  class Test {
         public  static  void main(String[] args)  throws ExecutionException, InterruptedException {
                 //建立一個線程池
                ExecutorService pool = Executors.newFixedThreadPool(2);
                 //建立兩個有返回值的任務
                Callable c1 =  new MyCallable( "A");
                Callable c2 =  new MyCallable( "B");
                 //執行任務並獲取Future對象
                Future f1 = pool.submit(c1);
                Future f2 = pool.submit(c2);
                 //從Future對象上獲取任務的返回值,並輸出到控制檯
                System.out.println( ">>>"+f1.get().toString());
                System.out.println( ">>>"+f2.get().toString());
                 //關閉線程池
                pool.shutdown();
        }
}

class MyCallable  implements Callable{
         private String oid;

        MyCallable(String oid) {
                 this.oid = oid;
        }

        @Override
         public Object call()  throws Exception {
                 return oid+ "任務返回的內容";
        }
}
 
>>>A任務返回的內容
>>>B任務返回的內容

Process finished with exit code 0
 
很是的簡單,要深刻了解還須要看Callable和Future接口的API啊。

Java線程:新特徵-鎖(上)
在Java5中,專門提供了鎖對象,利用鎖能夠方便的實現資源的封鎖,用來控制對競爭資源併發訪問的控制,這些內容主要集中在 java.util.concurrent.locks 包下面,裏面有三個重要的接口Condition、Lock、ReadWriteLock。
 
Condition Condition 將 Object 監視器方法(waitnotify 和 notifyAll)分解成大相徑庭的對象,以便經過將這些對象與任意 Lock 實現組合使用,爲每一個對象提供多個等待 set (wait-set)。
Lock Lock 實現提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做。
ReadWriteLock ReadWriteLock 維護了一對相關的鎖定,一個用於只讀操做,另外一個用於寫入操做。
 
有關鎖的介紹,API文檔解說不少,看得很煩,仍是看個例子再看文檔比較容易理解。
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public  class Test {
         public  static  void main(String[] args) {
                 //建立併發訪問的帳戶
                MyCount myCount =  new MyCount( "95599200901215522", 10000);
                 //建立一個鎖對象
                Lock lock =  new ReentrantLock();
                 //建立一個線程池
                ExecutorService pool = Executors.newCachedThreadPool();
                 //建立一些併發訪問用戶,一個信用卡,存的存,取的取,好熱鬧啊
                User u1 =  new User( "張三", myCount, -4000, lock);
                User u2 =  new User( "張三他爹", myCount, 6000, lock);
                User u3 =  new User( "張三他弟", myCount, -8000, lock);
                User u4 =  new User( "張三", myCount, 800, lock);
                 //在線程池中執行各個用戶的操做
                pool.execute(u1);
                pool.execute(u2);
                pool.execute(u3);
                pool.execute(u4);
                 //關閉線程池
                pool.shutdown();
        }
}


class User  implements Runnable {
         private String name;                 //用戶名
         private MyCount myCount;         //所要操做的帳戶
         private  int iocash;                  //操做的金額,固然有正負之分了
         private Lock myLock;                 //執行操做所需的鎖對象

        User(String name, MyCount myCount,  int iocash, Lock myLock) {
                 this.name = name;
                 this.myCount = myCount;
                 this.iocash = iocash;
                 this.myLock = myLock;
        }

         public  void run() {
                 //獲取鎖
                myLock.lock();
                 //執行現金業務
                System.out.println(name +  "正在操做" + myCount +  "帳戶,金額爲" + iocash +  ",當前金額爲"+ myCount.getCash());
                myCount.setCash(myCount.getCash() + iocash);
                System.out.println(name +  "操做" + myCount +  "帳戶成功,金額爲" + iocash +  ",當前金額爲"+ myCount.getCash());
                 //釋放鎖,不然別的線程沒有機會執行了
                myLock.unlock();
        }
}


class MyCount {
         private String oid;          //帳號
         private  int cash;              //帳戶餘額

        MyCount(String oid,  int cash) {
                 this.oid = oid;
                 this.cash = cash;
        }

         public String getOid() {
                 return oid;
        }

         public  void setOid(String oid) {
                 this.oid = oid;
        }

         public  int getCash() {
                 return cash;
        }

         public  void setCash( int cash) {
                 this.cash = cash;
        }

        @Override
         public String toString() {
                 return  "MyCount{" +
                                 "oid='" + oid + '\'' +
                                 ", cash=" + cash +
                                '}';
        }
}
 
張三正在操做MyCount{oid='95599200901215522', cash=10000}帳戶,金額爲-4000,當前金額爲10000
張三操做MyCount{oid='95599200901215522', cash=6000}帳戶成功,金額爲-4000,當前金額爲6000
張三他爹正在操做MyCount{oid='95599200901215522', cash=6000}帳戶,金額爲6000,當前金額爲6000
張三他爹操做MyCount{oid='95599200901215522', cash=12000}帳戶成功,金額爲6000,當前金額爲12000
張三他弟正在操做MyCount{oid='95599200901215522', cash=12000}帳戶,金額爲-8000,當前金額爲12000
張三他弟操做MyCount{oid='95599200901215522', cash=4000}帳戶成功,金額爲-8000,當前金額爲4000
張三正在操做MyCount{oid='95599200901215522', cash=4000}帳戶,金額爲800,當前金額爲4000
張三操做MyCount{oid='95599200901215522', cash=4800}帳戶成功,金額爲800,當前金額爲4800

Process finished with exit code 0
 
從上面的輸出能夠看到,利用鎖對象太方便了,比直接在某個不知情的對象上用鎖清晰多了。
 
但必定要注意的是,在獲取了鎖對象後,用完後應該儘快釋放鎖,以便別的等待該鎖的線程有機會去執行。

Java線程:新特徵-鎖(下)
在上文中提到了Lock接口以及對象,使用它,很優雅的控制了競爭資源的安全訪問,可是這種鎖不區分讀寫,稱這種鎖爲普通鎖。爲了提升性能,Java提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,在必定程度上提升了程序的執行效率。
 
Java中讀寫鎖有個接口java.util.concurrent.locks.ReadWriteLock,也有具體的實現ReentrantReadWriteLock,詳細的API能夠查看JavaAPI文檔。
 
下面這個例子是在文例子的基礎上,將普通鎖改成讀寫鎖,並添加帳戶餘額查詢的功能,代碼以下:
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


public  class Test {
         public  static  void main(String[] args) {
                 //建立併發訪問的帳戶
                MyCount myCount =  new MyCount( "95599200901215522", 10000);
                 //建立一個鎖對象
                ReadWriteLock lock =  new ReentrantReadWriteLock( false);
                 //建立一個線程池
                ExecutorService pool = Executors.newFixedThreadPool(2);
                 //建立一些併發訪問用戶,一個信用卡,存的存,取的取,好熱鬧啊
                User u1 =  new User( "張三", myCount, -4000, lock,  false);
                User u2 =  new User( "張三他爹", myCount, 6000, lock,  false);
                User u3 =  new User( "張三他弟", myCount, -8000, lock,  false);
                User u4 =  new User( "張三", myCount, 800, lock,  false);
                User u5 =  new User( "張三他爹", myCount, 0, lock,  true);
                 //在線程池中執行各個用戶的操做
                pool.execute(u1);
                pool.execute(u2);
                pool.execute(u3);
                pool.execute(u4);
                pool.execute(u5);
                 //關閉線程池
                pool.shutdown();
        }
}


class User  implements Runnable {
         private String name;                 //用戶名
         private MyCount myCount;         //所要操做的帳戶
         private  int iocash;                  //操做的金額,固然有正負之分了
         private ReadWriteLock myLock;                 //執行操做所需的鎖對象
         private  boolean ischeck;         //是否查詢

        User(String name, MyCount myCount,  int iocash, ReadWriteLock myLock,  boolean ischeck) {
                 this.name = name;
                 this.myCount = myCount;
                 this.iocash = iocash;
                 this.myLock = myLock;
                 this.ischeck = ischeck;
        }

         public  void run() {
                 if (ischeck) {
                         //獲取讀鎖
                        myLock.readLock().lock();
                        System.out.println( "讀:" + name +  "正在查詢" + myCount +  "帳戶,當前金額爲" + myCount.getCash());
                         //釋放讀鎖
                        myLock.readLock().unlock();
                }  else {
                         //獲取寫鎖
                        myLock.writeLock().lock();
                         //執行現金業務
                        System.out.println( "寫:" + name +  "正在操做" + myCount +  "帳戶,金額爲" + iocash +  ",當前金額爲" + myCount.getCash());
                        myCount.setCash(myCount.getCash() + iocash);
                        System.out.println( "寫:" + name +  "操做" + myCount +  "帳戶成功,金額爲" + iocash +  ",當前金額爲" + myCount.getCash());
                         //釋放寫鎖
                        myLock.writeLock().unlock();
                }
        }
}


class MyCount {
         private String oid;          //帳號
         private  int cash;              //帳戶餘額

        MyCount(String oid,  int cash) {
                 this.oid = oid;
                 this.cash = cash;
        }

         public String getOid() {
                 return oid;
        }

         public  void setOid(String oid) {
                 this.oid = oid;
        }

         public  int getCash() {
                 return cash;
        }

         public  void setCash( int cash) {
                 this.cash = cash;
        }

        @Override
         public String toString() {
                 return  "MyCount{" +
                                 "oid='" + oid + '\'' +
                                 ", cash=" + cash +
                                '}';
        }
}
 
寫:張三正在操做MyCount{oid='95599200901215522', cash=10000}帳戶,金額爲-4000,當前金額爲10000
寫:張三操做MyCount{oid='95599200901215522', cash=6000}帳戶成功,金額爲-4000,當前金額爲6000
寫:張三他弟正在操做MyCount{oid='95599200901215522', cash=6000}帳戶,金額爲-8000,當前金額爲6000
寫:張三他弟操做MyCount{oid='95599200901215522', cash=-2000}帳戶成功,金額爲-8000,當前金額爲-2000
寫:張三正在操做MyCount{oid='95599200901215522', cash=-2000}帳戶,金額爲800,當前金額爲-2000
寫:張三操做MyCount{oid='95599200901215522', cash=-1200}帳戶成功,金額爲800,當前金額爲-1200
讀:張三他爹正在查詢MyCount{oid='95599200901215522', cash=-1200}帳戶,當前金額爲-1200
寫:張三他爹正在操做MyCount{oid='95599200901215522', cash=-1200}帳戶,金額爲6000,當前金額爲-1200
寫:張三他爹操做MyCount{oid='95599200901215522', cash=4800}帳戶成功,金額爲6000,當前金額爲4800

Process finished with exit code 0
 
在實際開發中,最好在能用讀寫鎖的狀況下使用讀寫鎖,而不要用普通鎖,以求更好的性能。

Java線程:新特徵-信號量
Java的信號量其實是一個功能完畢的計數器,對控制必定資源的消費與回收有着很重要的意義,信號量經常用於多線程的代碼中,並能監控有多少 數目的線程等待獲取資源,而且經過信號量能夠得知可用資源的數目等等,這裏老是在強調「數目」二字,但不能指出來有哪些在等待,哪些資源可用。
 
所以,本人認爲,這個信號量類若是能返回數目,還能知道哪些對象在等待,哪些資源可以使用,就很是完美了,僅僅拿到這些歸納性的數字,對精確控制意義不是很大。目前還沒想到更好的用法。
 
下面是一個簡單例子:
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;


public  class Test {
         public  static  void 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;      //池相關的信號量

        
        MyPool( int size) {
                 this.sp =  new Semaphore(size);
        }

         public Semaphore getSp() {
                 return sp;
        }

         public  void setSp(Semaphore sp) {
                 this.sp = sp;
        }
}

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

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

         public  void 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個許可!

Process finished with exit code 0
 
從結果能夠看出,信號量僅僅是對池資源進行監控,但不保證線程的安全,所以,在使用時候,應該本身控制線程的安全訪問池資源。

Java線程:新特徵-阻塞隊列
阻塞隊列是Java5線程新特徵中的內容,Java定義了阻塞隊列的接口 java.util.concurrent.BlockingQueue,阻塞隊列的概念是,一個指定長度的隊列,若是隊列滿了,添加新元素的操做會被阻 塞等待,直到有空位爲止。一樣,當隊列爲空時候,請求隊列元素的操做一樣會阻塞等待,直到有可用元素爲止。
 
有了這樣的功能,就爲多線程的排隊等候的模型實現開闢了便捷通道,很是有用。
 
java.util.concurrent.BlockingQueue繼承了java.util.Queue接口,能夠參看API文檔。
 
下面給出一個簡單應用的例子:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;


public  class Test {
         public  static  void main(String[] args)  throws InterruptedException {
                BlockingQueue bqueue =  new ArrayBlockingQueue(20);
                 for ( int i = 0; i < 30; i++) {
                         //將指定元素添加到此隊列中,若是沒有可用空間,將一直等待(若是有必要)。
                        bqueue.put(i);
                        System.out.println( "向阻塞隊列中添加了元素:" + i);
                }
                System.out.println( "程序到此運行結束,即將退出----");
        }
}
 
輸出結果:
向阻塞隊列中添加了元素:0
向阻塞隊列中添加了元素:1
向阻塞隊列中添加了元素:2
向阻塞隊列中添加了元素:3
向阻塞隊列中添加了元素:4
向阻塞隊列中添加了元素:5
向阻塞隊列中添加了元素:6
向阻塞隊列中添加了元素:7
向阻塞隊列中添加了元素:8
向阻塞隊列中添加了元素:9
向阻塞隊列中添加了元素:10
向阻塞隊列中添加了元素:11
向阻塞隊列中添加了元素:12
向阻塞隊列中添加了元素:13
向阻塞隊列中添加了元素:14
向阻塞隊列中添加了元素:15
向阻塞隊列中添加了元素:16
向阻塞隊列中添加了元素:17
向阻塞隊列中添加了元素:18
向阻塞隊列中添加了元素:19
 
能夠看出,輸出到元素19時候,就一直處於等待狀態,由於隊列滿了,程序阻塞了。
 
這裏沒有用多線程來演示,沒有這個必要。
 
另外,阻塞隊列還有更多實現類,用來知足各類複雜的需求:ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue ,具體的API差異也很小。


Java線程:新特徵-阻塞棧

對於阻塞棧,與阻塞隊列類似。不一樣點在於棧是「後入先出」的結構,每次操做的是棧頂,而隊列是「先進先出」的結構,每次操做的是隊列頭。
 
這裏要特別說明一點的是,阻塞棧是Java6的新特徵。、
 
Java爲阻塞棧定義了接口:java.util.concurrent.BlockingDeque,其實現類也比較多,具體能夠查看JavaAPI文檔。
 
下面看一個簡單例子:
 
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;


public  class Test {
         public  static  void main(String[] args)  throws InterruptedException {
                BlockingDeque bDeque =  new LinkedBlockingDeque(20);
                 for ( int i = 0; i < 30; i++) {
                         //將指定元素添加到此阻塞棧中,若是沒有可用空間,將一直等待(若是有必要)。
                        bDeque.putFirst(i);
                        System.out.println( "向阻塞棧中添加了元素:" + i);
                }
                System.out.println( "程序到此運行結束,即將退出----");
        }
}
 
輸出結果:
向阻塞棧中添加了元素:0
向阻塞棧中添加了元素:1
向阻塞棧中添加了元素:2
向阻塞棧中添加了元素:3
向阻塞棧中添加了元素:4
向阻塞棧中添加了元素:5
向阻塞棧中添加了元素:6
向阻塞棧中添加了元素:7
向阻塞棧中添加了元素:8
向阻塞棧中添加了元素:9
向阻塞棧中添加了元素:10
向阻塞棧中添加了元素:11
向阻塞棧中添加了元素:12
向阻塞棧中添加了元素:13
向阻塞棧中添加了元素:14
向阻塞棧中添加了元素:15
向阻塞棧中添加了元素:16
向阻塞棧中添加了元素:17
向阻塞棧中添加了元素:18
向阻塞棧中添加了元素:19
 
從上面結果能夠看到,程序並沒結束,二是阻塞住了,緣由是棧已經滿了,後面追加元素的操做都被阻塞了。

摘自----- http://lavasoft.blog.51cto.com/62575/222742
相關文章
相關標籤/搜索