Java多線程(三)、線程同步

在之前,已經學習到了線程的創建和狀態控制,但是每個線程之間幾乎都沒有什麼太大的聯繫。可是有的時候,可能存在多個線程多同一個數據進行操作,這樣,可能就會引用各種奇怪的問題。現在就來學習多線程對數據訪問的控制吧。

 由於同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問衝突這個嚴重的問題。Java語言提供了專門機制以解決這種衝突,有效避免了同一個數據對象被多個線程同時訪問。


一、多線程引起的數據訪問安全問題

下面看一個經典的問題,銀行取錢的問題:

1)、你有一張銀行卡,裏面有5000塊錢,然後你到取款機取款,取出3000,當正在取的時候,取款機已經查詢到你有5000塊錢,然後正準備減去300塊錢的時候

2)、你的老婆拿着那張銀行卡對應的存摺到銀行取錢,也要取3000.然後銀行的系統查詢,存摺賬戶裏還有6000(因爲上面錢還沒扣),所以它也準備減去3000,

3)、你的卡里面減去3000,5000-3000=2000,並且你老婆的存摺也是5000-3000=2000。

4)、結果,你們一共取了6000,但是卡里還剩下2000。

下面看程序的模擬過程:

  1. package com.tao.test;  
  2.   
  3. public class GetMoneyTest {  
  4.     public static void main(String[] args) {  
  5.         Account account = new Account(5000);  
  6.         GetMoneyRun runnable = new GetMoneyRun(account);  
  7.         new Thread(runnable, "你").start();  
  8.         new Thread(runnable, "你老婆").start();  
  9.     }  
  10. }  
  11.   
  12. // 賬戶Mode   
  13. class Account {  
  14.     private int money;  
  15.   
  16.     public Account(int money) {  
  17.         super();  
  18.         this.money = money;  
  19.     }  
  20.   
  21.     public int getMoney() {  
  22.         return money;  
  23.     }  
  24.   
  25.     public void setMoney(int money) {  
  26.         this.money = money;  
  27.     }  
  28.   
  29. }  
  30. //runnable類   
  31. class GetMoneyRun implements Runnable {  
  32.     private Account account;  
  33.   
  34.     public GetMoneyRun(Account account) {  
  35.         this.account = account;  
  36.     }  
  37.   
  38.     @Override  
  39.     public void run() {  
  40.         if (account.getMoney() > 3000) {  
  41.             System.out.println(Thread.currentThread().getName() + "的賬戶有"  
  42.                     + account.getMoney() + "元");  
  43.             try {  
  44.                 Thread.sleep(10);  
  45.             } catch (InterruptedException e) {  
  46.                 e.printStackTrace();  
  47.             }  
  48.             int lasetMoney=account.getMoney() - 3000;  
  49.             account.setMoney(lasetMoney);  
  50.             System.out.println(Thread.currentThread().getName() + "取出來了3000元"  
  51.                     + Thread.currentThread().getName() + "的賬戶還有"  
  52.                     + account.getMoney() + "元");  
  53.   
  54.         } else {  
  55.             System.out.println("餘額不足3000" + Thread.currentThread().getName()  
  56.                     + "的賬戶只有" + account.getMoney() + "元");  
  57.         }  
  58.   
  59.     }  
  60.   
  61. }  
package com.tao.test;

public class GetMoneyTest {
	public static void main(String[] args) {
		Account account = new Account(5000);
		GetMoneyRun runnable = new GetMoneyRun(account);
		new Thread(runnable, "你").start();
		new Thread(runnable, "你老婆").start();
	}
}

// 賬戶Mode
class Account {
	private int money;

	public Account(int money) {
		super();
		this.money = money;
	}

	public int getMoney() {
		return money;
	}

	public void setMoney(int money) {
		this.money = money;
	}

}
//runnable類
class GetMoneyRun implements Runnable {
	private Account account;

	public GetMoneyRun(Account account) {
		this.account = account;
	}

	@Override
	public void run() {
		if (account.getMoney() > 3000) {
			System.out.println(Thread.currentThread().getName() + "的賬戶有"
					+ account.getMoney() + "元");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			int lasetMoney=account.getMoney() - 3000;
			account.setMoney(lasetMoney);
			System.out.println(Thread.currentThread().getName() + "取出來了3000元"
					+ Thread.currentThread().getName() + "的賬戶還有"
					+ account.getMoney() + "元");

		} else {
			System.out.println("餘額不足3000" + Thread.currentThread().getName()
					+ "的賬戶只有" + account.getMoney() + "元");
		}

	}

}
多次運行程序,可以看到有多種不同的結果,下面是其中的三種:

  1. 你的賬戶有5000元  
  2. 你老婆的賬戶有5000元  
  3. 你老婆取出來了3000元你老婆的賬戶還有2000元  
  4. 你取出來了3000元你的賬戶還有-1000元  
你的賬戶有5000元
你老婆的賬戶有5000元
你老婆取出來了3000元你老婆的賬戶還有2000元
你取出來了3000元你的賬戶還有-1000元

  1. 你的賬戶有5000元  
  2. 你老婆的賬戶有5000元  
  3. 你老婆取出來了3000元你老婆的賬戶還有-1000元  
  4. 你取出來了3000元你的賬戶還有-1000元  
你的賬戶有5000元
你老婆的賬戶有5000元
你老婆取出來了3000元你老婆的賬戶還有-1000元
你取出來了3000元你的賬戶還有-1000元

  1. 你的賬戶有5000元  
  2. 你老婆的賬戶有5000元  
  3. 你老婆取出來了3000元你老婆的賬戶還有2000元  
  4. 你取出來了3000元你的賬戶還有2000元  
你的賬戶有5000元
你老婆的賬戶有5000元
你老婆取出來了3000元你老婆的賬戶還有2000元
你取出來了3000元你的賬戶還有2000元

可以看到,由於有兩個線程同時訪問這個account對象,導致取錢發生的賬戶發生問題。當多個線程訪問同一個數據的時候,非常容易引發問題。爲了避免這樣的事情發生,我們要保證線程同步互斥,所謂同步互斥就是:併發執行的多個線程在某一時間內只允許一個線程在執行以訪問共享數據。


二、同步互斥鎖

同步鎖的原理:Java中每個對象都有一個內置同步鎖。Java中可以使用synchronized關鍵字來取得一個對象的同步鎖。synchronized的使用方式,是在一段代碼塊中,加上synchronized(object){ ... }

例如,有一個show方法,裏面有synchronized的代碼段:

  1. public void show() {  
  2.     synchronized(object){  
  3.        ......  
  4.     }  
  5. }  
public void show() {
    synchronized(object){
       ......
    }
}

這其中的object可以使任何對象,表示當前線程取得該對象的鎖。一個對象只有一個鎖,所以其他任何線程都不能訪問該對象的所有由synchronized包括的代碼段,直到該線程釋放掉這個對象的同步鎖(釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊)。

注意:synchronized使用方式有幾個要注意的地方(還是以上面的show方法舉例):

①、取得同步鎖的對象爲this,即當前類對象,這是使用的最多的一種方式

  1. public void show() {  
  2.     synchronized(this){  
  3.        ......  
  4.     }  
  5. }  
public void show() {
    synchronized(this){
       ......
    }
}


②、將synchronized加到方法上,這叫做同步方法,相當於第一種方式的縮寫
  1. public synchronized void show() {  
  2.      
  3. }  
public synchronized void show() {
   
}


③、靜態方法的同步

  1. public static synchronized void show() {  
  2.      
  3. }  
public static synchronized void show() {
   
}
相當於

  1. public static void show() {  
  2.    synchronized(當前類名.class)   
  3. }  
public static void show() {
   synchronized(當前類名.class) 
}
相當於取得類對象的同步鎖,注意它和取得一個對象的同步鎖不一樣



明白了同步鎖的原理和synchronized關鍵字的使用,那麼解決上面的取錢問題就很簡單了,我們只要對run方法裏面加上synchronized關鍵字就沒有問題了,如下:
  1. @Override  
  2.     public void run() {  
  3.         synchronized (account) {  
  4.             if (account.getMoney() > 3000) {  
  5.                 System.out.println(Thread.currentThread().getName() + "的賬戶有"  
  6.                         + account.getMoney() + "元");  
  7.                 try {  
  8.                     Thread.sleep(10);  
  9.                 } catch (InterruptedException e) {  
  10.                     e.printStackTrace();  
  11.                 }  
  12.                 int lasetMoney = account.getMoney() - 3000;  
  13.                 account.setMoney(lasetMoney);  
  14.                 System.out.println(Thread.currentThread().getName()  
  15.                         + "取出來了3000元" + Thread.currentThread().getName()  
  16.                         + "的賬戶還有" + account.getMoney() + "元");  
  17.   
  18.             } else {  
  19.                 System.out.println("餘額不足3000"  
  20.                         + Thread.currentThread().getName() + "的賬戶只有"  
  21.                         + account.getMoney() + "元");  
  22.             }  
  23.   
  24.         }  
  25.     }  
@Override
    public void run() {
        synchronized (account) {
            if (account.getMoney() > 3000) {
                System.out.println(Thread.currentThread().getName() + "的賬戶有"
                        + account.getMoney() + "元");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int lasetMoney = account.getMoney() - 3000;
                account.setMoney(lasetMoney);
                System.out.println(Thread.currentThread().getName()
                        + "取出來了3000元" + Thread.currentThread().getName()
                        + "的賬戶還有" + account.getMoney() + "元");

            } else {
                System.out.println("餘額不足3000"
                        + Thread.currentThread().getName() + "的賬戶只有"
                        + account.getMoney() + "元");
            }

        }
    }



當甲線程執行run方法的時候,它使用synchronized (account)取得了account對象的同步鎖,那麼只要它沒釋放掉這個鎖,那麼當乙線程執行到run方法的時候,它就不能獲得繼續執行的鎖,所以只能等甲線程執行完,然後釋放掉鎖,乙線程才能繼續執行。


synchronized關鍵字使用要注意以下幾點:

1)、只能同步方法和代碼塊,而不能同步變量和類。只要保護好類中數據的安全訪問和設置就可以了,不需要對類使用synchronized關鍵字,所以Java不允許這麼做。並且想要同步數據,只需要對成員變量私有化,然後同步方法即可,不需要對成員變量使用synchronized,java也禁止這麼做。

2)、每個對象只有一個同步鎖;當提到同步時,應該清楚在什麼上同步?也就是說,在哪個對象上同步?上面的代碼中run方法使用synchronized (account)代碼塊,因爲兩個線程訪問的都是同一個Account對象,所以能夠鎖定。但是如果是其他的一個無關的對象,就沒用了。比如說synchronized (new Date())代碼塊,一樣沒有效果。

3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。

4)、如果兩個線程要執行一個類中的synchronized方法,並且兩個線程使用相同的實例來調用方法,那麼一次只能有一個線程能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖,就沒有任何其他線程可以進入(該對象的)類中的任何一個同步方法。

5)、如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由訪問而不受鎖的限制。

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

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

8)、同步損害併發性,應該儘可能縮小同步範圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。

9)、編寫線程安全的代碼會使系統的總體效率會降低,要適量使用



一個線程取得了同步鎖,那麼在什麼時候纔會釋放掉呢?

1、同步方法或代碼塊正常結束

2、使用return或 break終止了執行,或者跑出了未處理的異常。

3、當線程執行同步方法或代碼塊時,程序執行了同步鎖對象的wait()方法。


三、死鎖

死鎖:多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由於線程被無限期地阻塞,因此程序不能正常運行。簡單的說就是:線程死鎖時,第一個線程等待第二個線程釋放資源,而同時第二個線程又在等待第一個線程釋放資源。這裏舉一個通俗的例子:如在人行道上兩個人迎面相遇,爲了給對方讓道,兩人同時向一側邁出一步,雙方無法通過,又同時向另一側邁出一步,這樣還是無法通過。假設這種情況一直持續下去,這樣就會發生死鎖現象。

    導致死鎖的根源在於不適當地運用“synchronized”關鍵詞來管理線程對特定對象的訪問。“synchronized”關鍵詞的作用是,確保在某個時刻只有一個線程被允許執行特定的代碼塊,因此,被允許執行的線程首先必須擁有對變量或對象的排他性訪問權。當線程訪問對象時,線程會給對象加鎖,而這個鎖導致其它也想訪問同一對象的線程被阻塞,直至第一個線程釋放它加在對象上的鎖。

一個死鎖的造成很簡單,比如有兩個對象A 和 B 。第一個線程鎖住了A,然後休眠1秒,輪到第二個線程執行,第二個線程鎖住了B,然後也休眠1秒,然後有輪到第一個線程執行。第一個線程又企圖鎖住B,可是B已經被第二個線程鎖定了,所以第一個線程進入阻塞狀態,又切換到第二個線程執行。第二個線程又企圖鎖住A,可是A已經被第一個線程鎖定了,所以第二個線程也進入阻塞狀態。就這樣,死鎖造成了。

舉個例子:

  1. package com.tao.test;  
  2.   
  3. public class DeadLock2 {  
  4.     public static void main(String[] args) {  
  5.         Object object1=new Object();  
  6.         Object object2=new Object();  
  7.         new Thread(new T(object1,object2)).start();  
  8.         new Thread(new T(object2,object1)).start();  
  9.     }  
  10. }  
  11. class T implements Runnable{  
  12.     private Object object1;  
  13.     private Object object2;  
  14.     public T(Object object1,Object object2) {  
  15.         this.object1=object1;  
  16.         this.object2=object2;  
  17.     }  
  18.     public void run() {  
  19.         synchronized (object1) {  
  20.             try {  
  21.                 Thread.sleep(1000);  
  22.             } catch (InterruptedException e) {  
  23.                 e.printStackTrace();  
  24.             }  
  25.             synchronized (object2) {  
  26.                 System.out.println("無法執行到這一步");  
  27.             }  
  28.         }  
  29.     };  
  30. }  
package com.tao.test;

public class DeadLock2 {
	public static void main(String[] args) {
		Object object1=new Object();
		Object object2=new Object();
		new Thread(new T(object1,object2)).start();
		new Thread(new T(object2,object1)).start();
	}
}
class T implements Runnable{
	private Object object1;
	private Object object2;
	public T(Object object1,Object object2) {
		this.object1=object1;
		this.object2=object2;
	}
	public void run() {
		synchronized (object1) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (object2) {
				System.out.println("無法執行到這一步");
			}
		}
	};
}
上面的就是個死鎖。

第一個線程首先鎖住了object1,然後休眠。接着第二個線程鎖住了object2,然後休眠。在第一個線程企圖在鎖住object2,進入阻塞。然後第二個線程企圖在鎖住object1,進入阻塞。死鎖了。



四、線程的協調運行

關於線程的協調運行,經典的例子就是生產者和消費者的問題。比如有生產者不斷的生產饅頭,放入一個籃子裏,而消費者不斷的從籃子裏拿饅頭吃。並且,當籃子滿的時候,生產者通知消費者來吃饅頭,並且自己等待不在生產饅頭。當籃子沒滿的的時候,由消費者通知生產者生產饅頭。這樣不斷的循環。

要完成上面的功能,光靠我們前面的同步等知識,是不能完成的。而是要用到線程間的協調運行。頂級父類Object中有3種方法來控制線程的協調運行。

notify、notifyAll、wait。其中wait有3個重載的方法。

這三個方法必須由同步監視器對象(即線程獲得的鎖對象)來調用,這可分爲兩種情況:

1、對於使用synchronized修飾的同步代碼塊,因爲當前的類對象(this)就是同步監視器,所以可以再同步方法中直接調用這三個方法。

2、對於使用synchronized修飾的同步代碼塊,同步監視器是synchronized後括號的對象,所以必須使用該對象調用這三個方法。

wait(): 導致當前線程等待,直到其他線程調用該同步監視器的notify()方法或notifyAll()方法來喚醒該線程。wait()方法有三種形式:無時間參數的wait(一直等待,直到其他線程通知),帶毫秒參數的wait和帶毫秒、微秒參數的wait(這兩種方法都是等待指定時間後自動甦醒)。調用wait()方法的當前線程會釋放對該同步監視器的鎖定。

notify(): 喚醒在此同步監視器上等待的單個線程。如果所有線程都在此同步監視器上等待,則會選擇幻想其中一個線程。選擇是任意性。只有當前線程放棄對該同步監視器的鎖定後(使用wait()方法),纔可以執行被喚醒的其他線程。

notifyAll():喚醒在此同步監視器上等待的所有線程。只有當前線程放棄對該同步監視器的鎖定後,纔可以執行被喚醒的線程。


因爲使用wait、notify和notifyAll三個方法一定是在同步代碼塊中使用的,所以一定要明白下面幾點:

1、如果兩個線程是因爲都要得到同一個對象的鎖,而導致其中一個線程進入阻塞狀態。那麼只有等獲得鎖的線程執行完畢,或者它執行了該鎖對象的wait方法,阻塞的線程纔會有機會得到鎖,繼續執行同步代碼塊。

2、使用wait方法進入等待狀態的線程,會釋放掉鎖。並且只有其他線程調用notify或者notifyAll方法,纔會被喚醒。要明白,線程因爲鎖阻塞和等待是不同的,因爲鎖進入阻塞狀態,會在其他線程釋放鎖的時候,得到鎖在執行。而等待狀態必須要靠別人喚醒,並且喚醒了也不一定會立刻執行,有可能因爲notifyAll方法使得很多線程被喚醒,多個線程等待同一個鎖,而進入阻塞狀態。還可能是調用notify的線程依然沒有釋放掉鎖,只有等他執行完了,其他線程才能去爭奪這個鎖。

看下面的例子:

  1. package com.tao.test;  
  2.   
  3. public class ThreadA {  
  4.     public static void main(String[] args) {  
  5.         RunnableTest myRunnanle=new RunnableTest();  
  6.        new Thread(myRunnanle).start();  
  7.         synchronized (myRunnanle) {  
  8.             try {  
  9.                 System.out.println("第一步");  
  10.                 myRunnanle.wait();  
  11.             } catch (InterruptedException e) {  
  12.                 e.printStackTrace();  
  13.             }  
  14.             System.out.println("第四步");  
  15.         }  
  16.     }  
  17. }  
  18.  class RunnableTest implements Runnable {  
  19.     public void run() {  
  20.         try {  
  21.             Thread.sleep(1);  
  22.         } catch (InterruptedException e) {  
  23.             e.printStackTrace();  
  24.         }  
  25.         synchronized (this) {  
  26.             System.out.println("第二步");  
  27.             notify();  
  28.             System.out.println("第三步");  
  29.         }  
  30.     }  
  31. }  
package com.tao.test;

public class ThreadA {
    public static void main(String[] args) {
    	RunnableTest myRunnanle=new RunnableTest();
       new Thread(myRunnanle).start();
        synchronized (myRunnanle) {
            try {
                System.out.println("第一步");
                myRunnanle.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第四步");
        }
    }
}
 class RunnableTest implements Runnable {
    public void run() {
    	try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
        synchronized (this) {
            System.out.println("第二步");
            notify();
            System.out.println("第三步");
        }
    }
}

有兩個線程,主線程和我們自己新建的子線程。一步步的分析程序的執行:

1、因爲子線程啓動後,調用了sleep,所以主線程先進入同步代碼塊,而子線程之後因爲沒有鎖,會進入阻塞狀態。

2、主線程的同步代碼塊執行,打印第一句話,然後調用wait方法,進入等待狀態。因爲進入了等待狀態,所以釋放掉了鎖,所以子線程可以獲得鎖,開始執行。

3、子線程執行,打印第二句話,然後調用notify方法,將主線程喚醒。可是子線程並沒有結束,依然持有鎖,所以主線程不得不進入阻塞狀態,等待這個鎖。

4、子線程打印第三句話,然後線程正常運行結束,釋放掉鎖。然後主線程得到了鎖,從阻塞進入運行狀態,打印第四句話。

5、完畢


在看一個關於上面提到的生產者和消費者的例子:

首先,是生產物品的Mode,這裏以饅頭舉例:

  1. // 饅頭的實例   
  2. class ManTou {  
  3.     private int id;// 饅頭的id   
  4.   
  5.     public ManTou(int id) {  
  6.         this.id = id;  
  7.     }  
  8.     public String toString(){  
  9.         return "ManTou"+id;  
  10.     }  
  11. }  
// 饅頭的實例
class ManTou {
	private int id;// 饅頭的id

	public ManTou(int id) {
		this.id = id;
	}
	public String toString(){
		return "ManTou"+id;
	}
}

共享對象,生產者生產的饅頭放入其中,消費者從裏面拿出饅頭,這裏以籃子舉例:

  1. // 籃子的實例,用來放饅頭   
  2. class BasketBall {  
  3.     private int index = 0;// 表示裝到第幾個了饅頭   
  4.     private ManTou[] manTous = new ManTou[6];// 可以放6個饅頭   
  5.   
  6.     // 放進去一個饅頭   
  7.     public synchronized void push(ManTou manTou) {  
  8.         while(index==manTous.length){  
  9.             try {  
  10.                 System.out.println("籃子滿了!");  
  11.                 this.wait();  
  12.             } catch (InterruptedException e) {  
  13.                 e.printStackTrace();  
  14.             }  
  15.         }  
  16.         System.out.println(Thread.currentThread().getName()+"生產"+manTou.toString());  
  17.         this.notify();  
  18.         manTous[index] = manTou;  
  19.         index++;  
  20.     }  
  21.   
  22.     // 拿一個饅頭   
  23.     public synchronized ManTou pop() {  
  24.         while (index==0) {  
  25.             try {  
  26.                 System.out.println("籃子空了!");  
  27.                 this.wait();  
  28.             } catch (InterruptedException e) {  
  29.                 e.printStackTrace();  
  30.             }  
  31.         }  
  32.         ManTou manTou=manTous[--index];  
  33.         System.out.println(Thread.currentThread().getName()+"吃了"+manTou.toString());  
  34.         this.notify();  
  35.         return manTou;  
  36.     }  
  37. }  
// 籃子的實例,用來放饅頭
class BasketBall {
	private int index = 0;// 表示裝到第幾個了饅頭
	private ManTou[] manTous = new ManTou[6];// 可以放6個饅頭

	// 放進去一個饅頭
	public synchronized void push(ManTou manTou) {
		while(index==manTous.length){
			try {
				System.out.println("籃子滿了!");
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"生產"+manTou.toString());
		this.notify();
		manTous[index] = manTou;
		index++;
	}

	// 拿一個饅頭
	public synchronized ManTou pop() {
		while (index==0) {
			try {
				System.out.println("籃子空了!");
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		ManTou manTou=manTous[--index];
		System.out.println(Thread.currentThread().getName()+"吃了"+manTou.toString());
		this.notify();
		return manTou;
	}
}

生產者:

  1. // 生產者,生產饅頭   
  2. class Producer implements Runnable {  
  3.     private BasketBall basketBall;  
  4.   
  5.     public Producer(BasketBall basketBall) {  
  6.         this.basketBall = basketBall;  
  7.     }  
  8.   
  9.     @Override  
  10.     public void run() {  
  11.         for (int i = 0; i < 20; i++) {  
  12.             ManTou manTou = new ManTou(i);// 生產饅頭   
  13.             basketBall.push(manTou);  
  14.             try {  
  15.                 Thread.sleep(500);  
  16.             } catch (InterruptedException e) {  
  17.                 e.printStackTrace();  
  18.             }  
  19.         }  
  20.     }  
  21. }  
// 生產者,生產饅頭
class Producer implements Runnable {
	private BasketBall basketBall;

	public Producer(BasketBall basketBall) {
		this.basketBall = basketBall;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			ManTou manTou = new ManTou(i);// 生產饅頭
			basketBall.push(manTou);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}


  1. // 消費者,拿饅頭吃   
  2. class Consumer implements Runnable {  
  3.     private BasketBall basketBall;  
  4.   
  5.     public Consumer(BasketBall basketBall) {  
  6.         this.basketBall = basketBall;  
  7.     }  
  8.   
  9.     @Override  
  10.     public void run() {  
  11.         for (int i = 0; i < 20; i++) {  
  12.             ManTou manTou=basketBall.pop();  
  13.             try {  
  14.                 Thread.sleep(1000);  
  15.             } catch (InterruptedException e) {  
  16.                 e.printStackTrace();  
  17.             }  
  18.         }  
  19.     }  
  20. }  
// 消費者,拿饅頭吃
class Consumer implements Runnable {
	private BasketBall basketBall;

	public Consumer(BasketBall basketBall) {
		this.basketBall = basketBall;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			ManTou manTou=basketBall.pop();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}


測試:

  1. public class ProducerConsumer {  
  2.     public static void main(String[] args) {  
  3.         BasketBall basketBall=new BasketBall();  
  4.         new Thread(new Producer(basketBall)).start();  
  5.         new Thread(new Consumer(basketBall)).start();  
  6.     }  
  7. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章