Java開發筆記(九十七)利用Runnable啓動線程

前面介紹了線程的基本用法,按理說足夠通常的場合使用了,只是每次開闢新線程,都得單獨定義專門的線程類,着實開銷不小。注意到新線程內部真正須要開發者重寫的僅有run方法,其實就是一段代碼塊,分線程啓動以後也單單執行該代碼段而已。於是徹底能夠把這段代碼抽出來,把它定義爲相似方法的一串任務代碼,這樣可以像調用公共方法同樣屢次調用這段代碼,也就無需另外定義新的線程類,只需命令已有的Thread去執行該代碼段就行了。
在Java中定義某個代碼段,則要藉助於接口Runnable,它是個函數式接口,惟一須要實現的只有run方法。之因此定義成函數式接口的形式,是由於要給任務方法套上面向對象的殼,這樣纔好由外部去調用封裝好的任務對象。如今有個階乘運算的任務,但願開個分線程計算式子「10!」的結果,那便定義一個實現了Runnable接口的任務類FactorialTask,並重寫run方法補充求解「10!」的代碼邏輯。編寫完成的FactorialTask類代碼示例以下:html

	// 定義一個求階乘的任務
	private static class FactorialTask implements Runnable {
		@Override
		public void run() {
			int product = 1;
			for (int i=1; i<=10; i++) {
				product *= i;
			}
			PrintUtils.print(Thread.currentThread().getName(), "階乘結果="+product);
		}
	}

 

接着建立FactorialTask類的任務對象,並經過線程類的構造方法傳入該任務,這就實現了在分線程中啓動階乘任務的功能。下面是外部給階乘任務開啓新線程的代碼例子:ide

		// 經過Runnable建立線程的第一種方式:傳入普通實例
		FactorialTask task = new FactorialTask();
		new Thread(task).start(); // 建立並啓動線程

 

鑑於階乘任務的實現代碼很短,似無必要定義專門的任務類,不妨循着比較器Comparator的舊例,採起匿名內部類的方式書寫更爲便捷。因而可在線程類Thread的構造方法中直接填入實現後的Runnable任務代碼,具體的調用代碼以下所示:函數

		// 經過Runnable建立線程的第二種方式:傳入匿名內部類的實例
		new Thread(new Runnable() {
			@Override
			public void run() {
				int product = 1;
				for (int i=1; i<=10; i++) {
					product *= i;
				}
				PrintUtils.print(Thread.currentThread().getName(), "階乘結果="+product);
			}
		}).start(); // 建立並啓動線程

 

因爲Runnable是函數式接口,所以徹底可使用Lambda表達式加以簡化,下面即是利用Lambda表達式取代匿名內部類的任務線程代碼:測試

		// 經過Runnable建立線程的第三種方式:使用Lambda表達式
		new Thread(() -> {
			int product = 1;
			for (int i=1; i<=10; i++) {
				product *= i;
			}
			PrintUtils.print(Thread.currentThread().getName(), "階乘結果="+product);
		}).start(); // 建立並啓動線程

雖然說Runnable接口的花樣會比直接從Thread派生的多一些,但Runnable方式依舊要求實現run方法,看起來像是換湯不換藥,感受即便沒有Runnable也不影響線程的運用,最多在編碼上有點繁瑣罷了。可事情沒這麼簡單,要知道引入線程的目的是爲了加快處理速度,多個線程同時運行的話,必然涉及到資源共享及其合理分配。好比火車站賣動車票,只有一個售票窗口賣票的話,明顯賣得慢,確定要多開幾個售票窗口,一塊兒賣票才賣得快。假設目前還剩一百張動車票,此時開了三個售票窗口,這樣等同於啓動了三個售票線程,每一個線程都在賣剩下的一百張票。假若不採起Runnable接口,而是直接定義新線程的話,售票線程的定義代碼應該相似下面這般:編碼

	// 單獨定義一個售票線程
	private static class TicketThread extends Thread {
		private int ticketCount = 100; // 可出售的車票數量
		public TicketThread(String name) {
			setName(name); // 設置當前線程的名稱
		}

		@Override
		public void run() {
			while (ticketCount > 0) { // 還有餘票可供出售
				ticketCount--; // 餘票數量減一
				// 如下打印售票日誌,包括售票時間、售票線程、當前餘票等信息
				String left = String.format("當前餘票爲%d張", ticketCount);
				PrintUtils.print(Thread.currentThread().getName(), left);
			}
		}
	}

而後分別建立並啓動三個售票線程,就像如下代碼所示的那樣:線程

		//建立多個線程分別啓動,三個線程每一個各賣100張,總共賣了300張票
		new TicketThread("售票線程A").start();
		new TicketThread("售票線程B").start();
		new TicketThread("售票線程C").start();

 

猜猜看,上面三個售票線程總共賣了多少張票,實地運行測試代碼後發現,這三個線程居然賣掉了三百張票,而不是指望的一百張餘票。究其緣由,乃是各線程售賣的車票爲專享而非共享,每一個線程只承認本身掌握的車票,不承認其它線程的車票,結果致使三個線程各賣各的,加起來一共賣了三百張票。因此單獨定義的線程類處理獨立的事務倒還湊合,要是處理共享的事務就難辦了。
若是採用Runnable接口來定義售票任務,就能夠很方便地進行資源共享,只要命令三個線程同時執行售票任務便可。下面是開啓三個線程運行售票任務的代碼例子:日誌

		//只建立一個售票任務,並啓動三個線程一塊兒執行售票任務,總共賣了100張票
		Runnable seller = new Runnable() {
			private int ticketCount = 100; // 可出售的車票數量
			@Override
			public void run() {
				while (ticketCount > 0) { // 還有餘票可供出售
					ticketCount--; // 餘票數量減一
					// 如下打印售票日誌,包括售票時間、售票線程、當前餘票等信息
					String left = String.format("當前餘票爲%d張", ticketCount);
					PrintUtils.print(Thread.currentThread().getName(), left);
				}
			}
		};
		new Thread(seller, "售票線程A").start(); // 啓動售票線程A
		new Thread(seller, "售票線程B").start(); // 啓動售票線程B
		new Thread(seller, "售票線程C").start(); // 啓動售票線程C

 

由於100張餘票位於同一個售票任務seller裏面,因此這些車票理應爲執行任務的線程們所共享。運行上述的任務測試代碼,觀察到以下的線程工做日誌:orm

16:27:21.077 售票線程C 當前餘票爲98張
16:27:21.083 售票線程A 當前餘票爲96張
16:27:21.083 售票線程C 當前餘票爲95張
16:27:21.077 售票線程B 當前餘票爲97張
………………………這裏省略中間的日誌……………………
16:27:21.118 售票線程B 當前餘票爲2張
16:27:21.118 售票線程A 當前餘票爲1張
16:27:21.118 售票線程C 當前餘票爲4張
16:27:21.118 售票線程B 當前餘票爲0張

 

可見此時三個售票線程一共賣掉了100張車票,才符合多窗口同時售票的預期功能。htm



更多Java技術文章參見《Java開發筆記(序)章節目錄對象

相關文章
相關標籤/搜索