在總結JDBC數據庫連接池的時候,發現Java多線程這塊掌握得不是很好,因此回頭看了下多線程的內容。做一下多線程模塊的學習和總結,穩固一下多線程這塊的基礎。關於多線程的一些理論知識,這裡不想啰嗦太多,可以進行下搜索了解。
使用Java多線程,總的來說方法有兩種:①繼承Thread類,重寫run方法 ②把相關的類實現Runnable(可運行)接口,重寫run方法。③實現Callable接口(相對用得較少)
1 package com.scl.thread; 2 3 public class TestThread 4 { 5 public static void main(String[] args) throws Exception 6 { 7 String mainName = Thread.currentThread().getName(); 8 System.out.println(mainName + "線程開始運行"); 9 10 // 方法1 使用繼承對線程進行調用 11 MyThread myThread = new MyThread(); 12 myThread.setName("wft"); 13 myThread.start(); 14 myThread.join(); 15 16 // 方法2 繼承Runnable接口進行調用 17 Thread thread = new Thread(new MyRunnable()); 18 thread.start(); 19 20 System.out.println(mainName + "線程結束"); 21 } 23 } 24 25 // 使用runnable開啟線程 26 class MyRunnable implements Runnable 27 { 28 @Override 29 public void run() 30 { 31 // 獲取調用線程的名字 32 String curName = Thread.currentThread().getName(); 33 System.out.println(curName + "線程啟動"); 34 try 35 { 36 Thread.sleep(1000); 37 System.out.println(curName + "正在運行"); 38 } 39 catch (InterruptedException e) 40 { 41 e.printStackTrace(); 42 } 43 System.out.println(curName + "線程結束"); 44 } 45 } 46 47 // 使用Thread進行run方法的編寫,開啟線程 48 class MyThread extends Thread 49 { 50 // Thread.currentThread()調用這個方法的線程名稱。 51 String s = Thread.currentThread().getName(); // 調用這個類的是main方法調用,run方法交由新線程操作 52 53 public MyThread() 54 { 55 System.out.println("Thread.currentThread().getname()=" + Thread.currentThread().getName()); 56 // 線程沒運行前初始化的名字.因為類的初始化跟線程運行無關,這裡返回的是處世話的名字 57 System.out.println("This.getName=" + this.getName()); 58 } 59 60 @Override 61 public void run() 62 { 63 // run方法交由新線程操作,所以調用的名稱不是main 64 String strName = Thread.currentThread().getName(); 65 System.out.println("this.getName " + this.getName()); 66 // main 67 System.out.println("do u no s?" + s); 68 69 System.out.println(strName + " 開始數數"); 70 for (int i = 0; i < 100; i++) 71 { 72 System.out.println(strName + " " + i); 73 } 74 System.out.println(strName + " 數數完畢"); 75 } 76 77 // 返回設置的name 78 public String bName() 79 { 80 return this.getName(); 81 } 82 }
最應該注意的是給線程標注名稱的時候,應該多使用Thread.currentThread().getname() 來獲取線程名稱。使用this.getName方法很容易出錯。
主要該注意的有如下兩個:
① 獲取名稱不在run方法裡面,永遠返回名稱為main。因為在構造線程類的時候,主線程main會進行代碼的初始化。run方法由新創建起來的線程去調用,這樣run方法裡面的名稱才能被正確獲取到
② 在繼承類的構造函數內,如上面MyThread的構造函數內調用getName()方法。該方法是由JDK 源碼Thread類繼承而來。new一個Thread實例時,系統���調用Thread
下的方法,代碼如下
//Thread 類構造函數 public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } // 私有的init方法 private void init(ThreadGroup g, Runnable target, String name,long stackSize) { init(g, target, name, stackSize, null); } //init 方法. 更詳細內容見JDK源碼Thread類363行 private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc)
所以在構造器內的初始化的時候調用init方法給線程設置name字段。導致參數名稱修改且以"Thread-"開頭。初始化完畢後,如果用戶設置過相關的線程名稱,系統會把該字段進行修改,可以運用上述代碼中的bName()函數進行驗證。
在使用Java多線程的時候,不要直接調用某個實例下的run方法,如果直接使用run方法。則直接調用,不會開辟新的線程進行功能操作。
繼承Thread和實現Runnable接口的區別:
① 繼承Thread能夠簡單地實現線程開辟,但由於Java只支持單繼承。繼承Thread方法實現線程會有比較多的局限性
② 實現Runnable接口能夠很好的將線程代碼和業務邏輯代碼分離,但代碼結構相對復雜。一般都會使用實現Runnable方法創建線程。
① join( ) 多線程是有cpu分配執行時間片段,如果要控制線程操作流程,則需要使用到join方法。join方法會阻塞主線程,主線程會等待join方法的線程執行完畢再繼續執行後續的操作。同時join方法可以添加時間,讓主線程等待一段時間,如果時間過了。則不再等待繼續執行。如:thread.join( ),當主線程編譯到該代碼時,主線程會停止下來等待thread線程執行完畢後,主線程再繼續後續操作
② sleep( ) 不釋放鎖資源,讓調用的線程進行等待。如:thread.sleep( ) ,但代碼執行到這一句時,thread會停止下來等待,直到被喚醒或者等到到sleep到的時間點後再進行後續操作。
③ wait( )和notify( )、notifyAll( ) 。這三個方法主要處理線程同步數據時使用。數據同步,也就是幾條線程在同時修改一份共享數據。如火車站5名銷售員同時在售賣火車票,火車票是固定數量的前提下,如何實現這5名銷售員同時知道火車票庫存、同一路段火車票售賣、車票信息共享等。
wait方法使當前線程暫停執行並釋放對象鎖標志。notify和notifyall方法會喚醒幾個等待的線程,讓線程重新獲取鎖代碼以便後續進行。線程狀態圖表如下:
關於使用鎖以及線程狀態內容,將在下一篇文章中進行總結(http://www.linuxidc.com/Linux/2016-07/133403.htm),上述內容若有問題,煩請指出糾正。