java學習--多線程

 Java是少數的集中支持多線程的語言之一,大多數的語言智能運行單獨的一個程序塊,無法同時運行不同的多個程序塊,Java的多線程機制彌補了這個缺憾,它可以讓不同的程序塊一起運行,這樣可以讓程序運行更加順暢,同時也達到了多任務處理的目的。

  一、線程和進程的概念

  現在的操作系統是多任務操作系統。多線程是實現多任務的一種方式。

  進程是程序的一個動態執行過程,是指一個內存中運行的應用程序,每個進程都有自己獨立的一塊內存空間,一個進程中可以啓動多個線程。比如在 Windows系統中,一個運行的exe就是一個進程。線程是指進程中的一個執行流程,一個進程中可以運行多個線程。比如java.exe進程中可以運行很多線程。線程總是屬於某個進程,進程中的多個線程共享進程的內存。“同時”執行是人的感覺,在線程之間實際上輪換執行。


  二、Java中線程的實現

  在Java中想實現多線程有兩種手段,一種是集成Thread類,另一種就是實現Runnable接口。下面看繼承自Thread類線程的創建原理。

  首先定義一個線程類,該類必須繼承自Thread類,同時必須明確的覆寫run()方法,如: 

class MyThread extends Thread{

  public void run(){ //覆寫Thread類中的run方法此方法是線程中
  線程主體; 
  }
}


然後定義一個主類,實例化線程類的對象,發動啓動線程的命令,如: 

public class ThreadText{

  public stataic void main(String args[]){

  MyThread m1=new MyThread();//實例化對象

  m1.start();//啓動多線程

  }
}



實現Runnable接口,首先定義一個線程類繼承自Runnable接口,如: 

class MyThread implements Runnable{

  public void run(){ //覆寫Runnable接口中的run方法

  線程主體;

  }
}


然後定義一個主類,實例化線程類的對象,發動啓動線程的命令,如: 

public class ThreadText{

  public stataic void main(String args[]){

  MyThread m1=new MyThread();//實例化Runnable子類對象

  Thread t1=new Thread(m1);//實例化Thread類對象

  t1.start();//啓動多線程

  }
}



線程的兩種實現方式,通過實現Runnable接口的線程方式可以實現資源的共享,而繼承Thread則不可以,原因何在?先看下面兩段代碼:

通過Thread實現線程:

Java代碼 複製代碼
  1. //使用Thread實現線程不能實現資源共享 
[java] view plaincopy
  1. //使用Thread實現線程不能實現資源共享  
  2. class MyThread extends Thread  
  3. {     
  4.     private int ticket=5;  
  5.     private String name;  
  6.     public MyThread(String name ){  
  7.         this.name=name;  
  8.     }  
  9.     public void run(){  
  10.         for(int i=0;i<10;i++){  
  11.             if(ticket>5){  
  12.                 System.out.println("線程"+name+"賣票"+i);  
  13.             }  
  14.         }  
  15.       
  16.     }  
  17. }  
  18.   
  19. public class ThreadDemo02  
  20. {  
  21.     public static void main(String args[]){  
  22.         MyThread A = new MyThread("A");    
  23.         MyThread B = new MyThread("B");  
  24.         A.start();  
  25.         B.start();  
  26.     }  
  27. }  

通過Runnable實現:

Java代碼 複製代碼
  1.  
    [java] view plaincopy
    1. //使用Runnable實現線程可以實現資源共享  
    2. class MyThread implements Runnable  
    3. {  
    4.     private int ticket=5;  
    5.     private String name;  
    6.     public MyThread(String name){  
    7.         this.name=name;  
    8.     }  
    9.     public void run(){  
    10.         for(int i=1;i<=10;i++){  
    11.             if(ticket>0){  
    12.                 System.out.println("線程"+name+"賣票"+(ticket--));  
    13.                 }  
    14.         }  
    15.     }  
    16. }  
    17. public class RunnableDemo02  
    18. {  
    19.     public static void main(String args[]){  
    20.         MyThread A = new MyThread("A");  //實例化線程要執行的任務  
    21.         Thread Ta = new Thread(A);    //實例兩個線程對象,實際傳遞的是一個任務  
    22.         Thread Tb = new Thread(A);    //因爲兩個線程執行的是一個任務,所以資源是共享的  
    23.         Ta.start();  
    24.         Tb.start();  
    25.     }  
    26. }  

解釋:

因爲一個線程只能啓動一次,通過Thread實現線程時,線程和線程所要執行的任務是捆綁在一起的。也就使得一個任務只能啓動一個線程,不同的線程執行的任務是不相同的,所以沒有必要,也不能讓兩個線程共享彼此任務中的資源。

一個任務可以啓動多個線程,通過Runnable方式實現的線程,實際是開闢一個線程,將任務傳遞進去,由此線程執行。可以實例化多個 Thread對象,將同一任務傳遞進去,也就是一個任務可以啓動多個線程來執行它。這些線程執行的是同一個任務,所以他們的資源是共享。

兩種不同的線程實現方式本身就決定了其是否能進行資源共享。


個人理解:網上很多文章講述可以使用Runnable實現資源共享,當然有的使用繼承Thread也可以,但我還是表示懷疑這種做法是否真的可以實現資源共享,在共享資源的過程中並沒有使用同步字段,這樣是否會引起資源衝突,還需進一步探討,如有牛人解釋,不勝感激!

我麼知道Java傳統多線程的實現有兩種方法,繼承Thread類或者實現Runnable即可.線程啓動時調用start()方法.

實現Runnable接口相比繼承Thread類有如下好處:

1.避免單繼承的侷限,一個類可以同時實現多個接口

2.適合資源的共享.


線程的同步

Volatile 是保證多個線程之間變量可見性的,也就是說一個線程對變量進行了寫操作,另外一個線程能夠獲取它最新的值。

它的工作原理是,它對寫和讀都是直接操作工作主存的。(這個可以通過操作字節碼看到)


進行多線程編程,同步控制是非常重要的,而同步控制就涉及到了鎖。

       對代碼進行同步控制我們可以選擇同步方法,也可以選擇同步塊,這兩種方式各有優缺點,至於具體選擇什麼方式,就見仁見智了,同步塊不僅可以更加精確的控制對象鎖,也就是控制鎖的作用域,何謂鎖的作用域?鎖的作用域就是從鎖被獲取到其被釋放的時間。而且可以選擇要獲取哪個對象的對象鎖。但是如果在使用同步塊機制時,如果使用過多的鎖也會容易引起死鎖問題,同時獲取和釋放所也有代價,而同步方法,它們所擁有的鎖就是該方法所屬的類的對象鎖,換句話說,也就是this對象,而且鎖的作用域也是整個方法,這可能導致其鎖的作用域可能太大,也有可能引起死鎖,同時因爲可能包含了不需要進行同步的代碼塊在內,也會降低程序的運行效率。而不管是同步方法還是同步塊,我們都不應該在他們的代碼塊內包含無限循環,如果代碼內部要是有了無限循環,那麼這個同步方法或者同步塊在獲取鎖以後因爲代碼會一直不停的循環着運行下去,也就沒有機會釋放它所獲取的鎖,而其它等待這把鎖的線程就永遠無法獲取這把鎖,這就造成了一種死鎖現象。


簡單自己總結一下:同步方法的鎖是this,靜態同步方法是該類 ;同步代碼塊的鎖自己規定,在syn後()中定義。


 詳細解說一下同步方法的鎖,同步方法分爲靜態同步方法與非靜態同步方法。

       所有的非靜態同步方法用的都是同一把鎖——實例對象本身,也就是說如果一個實例對象的非靜態同步方法獲取鎖後,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖後才能獲取鎖,可是別的實例對象的非靜態同步方法因爲跟該實例對象的非靜態同步方法用的是不同的鎖,所以毋須等待該實例對象已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。

        而所有的靜態同步方法用的也是同一把鎖——類對象本身,這兩把鎖是兩個不同的對象,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。但是一旦一個靜態同步方法獲取鎖後,其他的靜態同步方法都必須等待該方法釋放鎖後才能獲取鎖,而不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間,只要它們同一個類的實例對象!

    而對於同步塊,由於其鎖是可以選擇的,所以只有使用同一把鎖的同步塊之間纔有着競態條件,這就得具體情況具體分析了,但這裏有個需要注意的地方,同步塊的鎖是可以選擇的,但是不是可以任意選擇的!!!!這裏必須要注意一個物理對象和一個引用對象的實例變量之間的區別!使用一個引用對象的實例變量作爲鎖並不是一個好的選擇,因爲同步塊在執行過程中可能會改變它的值,其中就包括將其設置爲null,而對一個null對象加鎖會產生異常,並且對不同的對象加鎖也違背了同步的初衷!這看起來是很清楚的,但是一個經常發生的錯誤就是選用了錯誤的鎖對象,因此必須注意:同步是基於實際對象而不是對象引用的!多個變量可以引用同一個對象,變量也可以改變其值從而指向其他的對象,因此,當選擇一個對象鎖時,我們要根據實際對象而不是其引用來考慮!作爲一個原則,不要選擇一個可能會在鎖的作用域中改變值的實例變量作爲鎖對象!!!!

 

Java線程:線程的同步與鎖

 
一、同步問題提出
 
線程的同步是爲了防止多個線程訪問一個數據對象時,對數據造成的破壞。
例如:兩個線程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關鍵字同步方法或代碼。
 
二、同步和鎖定
 
1、鎖的原理
 
Java中每個對象都有一個內置鎖
 
         當程序運行到非靜態的synchronized同步方法上時,自動獲得與正在執行代碼類的當前實例(this實例)有關的鎖。獲得一個對象的鎖也稱爲獲取鎖、鎖定對象、在對象上鎖定或在對象上同步。
 
當程序運行到synchronized同步方法或代碼塊時才該對象鎖才起作用。
 
            一個對象只有一個鎖。所以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味着任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。
 
釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。
 
關於鎖和同步,有一下幾個要點:
1)、只能同步方法,而不能同步變量和類;
2)、每個對象只有一個鎖;當提到同步時,應該清楚在什麼上同步?也就是說,在哪個對象上同步?
3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4)、如果兩個線程要執行一個類中的synchronized方法,並且兩個線程使用相同的實例來調用方法,那麼一次只能有一個線程能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖,就沒有任何其他線程可以進入(該對象的)類中的任何一個同步方法
5)、如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由訪問而不受鎖的限制。

6)、線程睡眠時,它所持的任何鎖都不會釋放。

7)、線程可以獲得多個重進入(synchronized )鎖。比如,在一個對象的同步方法裏面調用另外一個對象的同步方法,則獲取了兩個對象的同步鎖。

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考試範圍了。
 
六、線程安全類
 
      當一個類已經很好的同步以保護它的數據時,這個類就稱爲“線程安全的”。
 即使是線程安全類,也應該特別小心,因爲操作的線程是間仍然不一定安全。
 
舉個形象的例子,比如一個集合是線程安全的,有兩個線程在操作同一個集合對象,當第一個線程查詢集合非空後,刪除集合中所有元素的時候。第二個線程也來執行與第一個線程相同的操作,也許在第一個線程查詢後,第二個線程也查詢出集合非空,但是當第一個執行清除後,第二個再執行刪除顯然是不對的,因爲此時集合已經爲空了。
看個代碼:
 
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; 
        } 
    } 
}


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

線程的狀態

1. 新建狀態(New):新創建了一個線程對象。
2. 就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
3. 運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
4. 阻塞狀態(Blocked):阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
5. 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
二. 線程狀態圖


多線程間的通信

線程間的相互作用:線程之間需要一些協調通信,來共同完成一件任務。

  Object類中相關的方法有兩個notify方法和三個wait方法:

  http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

  因爲wait和notify方法定義在Object類中,因此會被所有的類所繼承。

  這些方法都是final的,即它們都是不能被重寫的,不能通過子類覆寫去改變它們的行爲。


wait()方法

  wait()方法使得當前線程必須要等待,等到另外一個線程調用notify()或者notifyAll()方法。

  當前的線程必須擁有當前對象的monitor,也即lock,就是鎖。

  線程調用wait()方法,釋放它對鎖的擁有權,然後等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法),這樣它才能重新獲得鎖的擁有權和恢復執行。

  要確保調用wait()方法的時候擁有鎖,即,wait()方法的調用必須放在synchronized方法或synchronized塊中。

 

  一個小比較:

  當線程調用了wait()方法時,它會釋放掉對象的鎖。

  另一個會導致線程暫停的方法:Thread.sleep(),它會導致線程睡眠指定的毫秒數,但線程在睡眠的過程中是不會釋放掉對象的鎖的。

 

notify()方法

  notify()方法會喚醒一個等待當前對象的鎖的線程。

  如果多個線程在等待,它們中的一個將會選擇被喚醒。這種選擇是隨意的,和具體實現有關。(線程等待一個對象的鎖是由於調用了wait方法中的一個)。

  被喚醒的線程是不能被執行的,需要等到當前線程放棄這個對象的鎖。

  被喚醒的線程將和其他線程以通常的方式進行競爭,來獲得對象的鎖。也就是說,被喚醒的線程並沒有什麼優先權,也沒有什麼劣勢,對象的下一個線程還是需要通過一般性的競爭。

  notify()方法應該是被擁有對象的鎖的線程所調用。

  (This method should only be called by a thread that is the owner of this object's monitor.)

  換句話說,和wait()方法一樣,notify方法調用必須放在synchronized方法或synchronized塊中。

 

  wait()和notify()方法要求在調用時線程已經獲得了對象的鎖,因此對這兩個方法的調用需要放在synchronized方法或synchronized塊中。

  一個線程變爲一個對象的鎖的擁有者是通過下列三種方法:

  1.執行這個對象的synchronized實例方法。

  2.執行這個對象的synchronized語句塊。這個語句塊鎖的是這個對象。

  3.對於Class類的對象,執行那個類的synchronized、static方法。

 

程序實例

  利用兩個線程,對一個整形成員變量進行變化,一個對其增加,一個對其減少,利用線程間的通信,實現該整形變量0101這樣交替的變更。

public class NumberHolder
{
    private int number;

    public synchronized void increase()
    {
        if (0 != number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }

        // 能執行到這裏說明已經被喚醒
        // 並且number爲0
        number++;
        System.out.println(number);

        // 通知在等待的線程
        notify();
    }

    public synchronized void decrease()
    {
        if (0 == number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

        }

        // 能執行到這裏說明已經被喚醒
        // 並且number不爲0
        number--;
        System.out.println(number);
        notify();
    }

}



public class IncreaseThread extends Thread
{
    private NumberHolder numberHolder;

    public IncreaseThread(NumberHolder numberHolder)
    {
        this.numberHolder = numberHolder;
    }

    @Override
    public void run()
    {
        for (int i = 0; i < 20; ++i)
        {
            // 進行一定的延時
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            // 進行增加操作
            numberHolder.increase();
        }
    }

}



public class DecreaseThread extends Thread
{
    private NumberHolder numberHolder;

    public DecreaseThread(NumberHolder numberHolder)
    {
        this.numberHolder = numberHolder;
    }

    @Override
    public void run()
    {
        for (int i = 0; i < 20; ++i)
        {
            // 進行一定的延時
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            // 進行減少操作
            numberHolder.decrease();
        }
    }

}



public class NumberTest
{
    public static void main(String[] args)
    {
        NumberHolder numberHolder = new NumberHolder();
        
        Thread t1 = new IncreaseThread(numberHolder);
        Thread t2 = new DecreaseThread(numberHolder);
                
        t1.start();
        t2.start();
    }

}


  

  

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