使用Runnable接口創建多線程

使用Runnable接口創建多線程

  • 適合多個相同的程序代碼的線程去處理同一資源的情況,把虛擬CPU(線程)同程序的代碼、數據有效的分離,較好地體現了面向對象的設計思想
  • 可以避免由於java的單繼承特性帶來的侷限。我們經常碰到這樣一種情況,即當我們要將已經繼承了某一個類的之類放入多線程中,由於一個類不可能同時有兩個父類,所以不能用繼承Thread類的方式,那麼,這個類就只能採用實現Runnable
  • 當線程被構造時,需要的代碼和數據通過一個對象作爲構造函數實參傳遞進去,這個對象就是實現了Runnable接口的類的實例。
  •  事實上,幾乎所有多線程應用都可用Runnable接口方式。
下面這段代碼是張老師的一個車票例子:
package blackhouse.thread;

public class ThreadDemo1 {
	public static void main(String[] args) {
		TestThread tt = new TestThread();
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
	}
}

class TestThread implements Runnable {
	int tickets = 100;
	@Override
	
	public void run() {
			while (true) {			
		     synchronized (str) {
					if (tickets > 0) {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "  is saling ticket" + tickets--);
					}	
	      	}
		}
	}
}

多線程在實際中的應用

  • 網絡聊天程序的收發
  • 表記錄的中途取消
  • www服務器爲每一個來訪者都建立專屬服務

線程同步問題

上面的票號可能出現負數

1.解決的辦法是使用synchronized語句塊——同步代碼塊

將上面的部分代碼重寫如下:

String str = new String("");
@Override
public void run() {
		while (true) {			
	     synchronized (str) {
				if (tickets > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "  is saling ticket" + tickets--);
				}	
      	}
	}
}

線程同步的原理:

str這個對象也被稱爲監視器,並且多個線程的這個監視器應該是同一個。

所以這個String str = new String("");這一段不能放在run()方法中。

首先我們規定str這個對象的標誌位爲1時不可以執行下面的程序片,爲0時可以。

synchronized 關鍵字把str對象的標誌位置爲0,第一個線程進入時得到鎖旗標,並把對象標誌置爲0,其他線程再進入時進入線程池等待第一個線程歸還其鎖旗標,然後再執行代碼片。

在這個程序中,同步會犧牲程序的性能。因爲在一點時間點上,CPU只能執行一個線程,但是同步會消耗系統線程調度時間。


除了使用同步代碼塊外,我們還可以使用同步函數。

我們把run方法中的if語句塊抽取出來構造一個sale()方法如下:

	public synchronized void sale() {
			if (tickets > 0) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()
						+ "  is saling ticket" + tickets--);
			}
		}

然後再在run()方法中調用這個方法也可以達到線程同步。

public void run() {
		while (true) {		
			sale();
		}
	}

現在我們來想一想這個問題,我們知道同步機制是通過對象的標誌位來實現的,那麼我們定義的sale()方法是哪個對象的標誌位呢?我們不妨看看下面的這段代碼:

package blackhouse.thread;

public class ThreadDemo1 {
	public static void main(String[] args) {
		TestThread tt = new TestThread();
		new Thread(tt).start();
		tt.str="method";
		new Thread(tt).start();
 	}
}

class TestThread implements Runnable {
	int tickets = 100;
	String str = new String("");

	@Override
	public void run() {
		if (str.equals("method")) {
			while (true) {
				sale();
			}
		} else {
			while (true) {
				synchronized (str) {
					if (tickets > 0) {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "  is saling ticket" + tickets--);
					}
				}
			}
		}

	}

	public synchronized void sale() {
		if (tickets > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()
					+ "  is saling ticket" + tickets--);
		}
	}
}

可能你會認爲這個程序的線程是不同步的,我們來看看執行的結果

Thread-0  is saling ticket100
Thread-0  is saling ticket99
Thread-1  is saling ticket98
Thread-1  is saling ticket97
Thread-1  is saling ticket……
Thread-1  is saling ticket2
Thread-1  is saling ticket1

我們發現這兩個線程是同步的。並不是一個執行同步代碼塊,一個執行同步方法,他們可能都是執行的同步代碼塊或都是執行的同步方法。如果他們都是執行的同步方法,那麼他們的同步監視器都是使用的this對象,如果他們都是執行的同步代碼塊,那麼他們都是使用的str對象作爲監視器。那麼他究竟是執行了那塊synchronized 呢?

我們接下來在sale()方法中加入一條語句以示區別:

	System.out.print("sale()");
	System.out.println(Thread.currentThread().getName()+" is saling ticket "  +    tickets--);

結果輸出如下:

sale()Thread-0  is saling ticket100
sale()Thread-0  is saling ticket……
sale()Thread-1  is saling ticket2
sale()Thread-1  is saling ticket1

那麼就是說 if (str.equals("method")) 這條語句一開始就是成立的。要知道我們只是在第一個線程啓動之後把str 改爲method。

new Thread(tt).start();  語句只是說明這個線程出於就緒狀態。main()線程可能還在往下面執行tt.str="method";

我們來將主線程暫停一下看看:

TestThread tt = new TestThread();
		new Thread(tt).start();
				try
				{
					Thread.sleep(1);
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}
	tt.str = "method";
	new Thread(tt).start();

輸出結果:

Thread-0  is saling ticket100
sale()Thread-1  is saling ticket99
Thread-0  is saling ticket98
sale()Thread-1  is saling ticket97
Thread-0  is saling ticket35
sale()Thread-1  is saling ticket34
……………………………………
sale()Thread-1  is saling ticket6
Thread-0  is saling ticket5
sale()Thread-1  is saling ticket4
sale()Thread-1  is saling ticket3
Thread-0  is saling ticket2
sale()Thread-1  is saling ticket1
Thread-0  is saling ticket0

最後輸出的是ticket0,說明線程不同步。和我們最初的推理相同,我們再來把同步代碼塊的str改爲this,輸出結果爲:

Thread-0  is saling ticket100
Thread-0  is saling ticket99
sale()Thread-1  is saling ticket98
……………………
sale()Thread-1  is saling ticket90
Thread-0  is saling ticket89
Thread-0  is saling ticket88
……………………
Thread-0  is saling ticket3
Thread-0  is saling ticket2
Thread-0  is saling ticket1——————>最後爲ticket1

我們發現又是同步的了,這樣代碼塊與函數就同步了。說明sale()函數的監視器對象就是this 。


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