1.建立多線程幾種方式
1.1 繼承Thread,重寫父類的run()方法
Java使用
java.lang.Thread
類代表線程,所有的線程對象都必須是Thread類或其子類的執行個體。每個線程的作用是完成一定的任務,實際上就是執行一段程式流即一段順序執行的代碼。Java使用線程執行體來代表這段程式流。Java中通過繼承Thread類來建立并啟動多線程的步驟如下:
- 定義Thread類的子類,并重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,是以把run()方法稱為線程執行體。
- 建立Thread子類的執行個體,即建立了線程對象
- 調用線程對象的start()方法來啟動該線程
代碼如下:
- 自定義線程類
/** * @author bruceliu * @create 2019-05-29 23:15 * @description 繼承Thread,重寫父類的run()方法 */ public class MyThread extends Thread { /* * 利用繼承中的特點 * 将線程名稱傳遞 進行設定 */ public MyThread(String name) { super(name); } /* * 重寫run方法 * 定義線程要執行的代碼 */ public void run() { for (int i = 0; i < 20; i++) { //getName()方法 來自父親 System.out.println(getName() + i); } } }
- 測試類
/** * @author bruceliu * @create 2019-05-29 23:17 * @description 繼承Thread,重寫父類的run()方法 */ public class TestMyThread { public static void main(String[] args) { System.out.println("這裡是main線程"); MyThread mt = new MyThread("小強"); mt.start();//開啟了一個新的線程 for (int i = 0; i < 20; i++) { System.out.println("旺财:"+i); } } }
- 流程圖
并發程式設計專題(二)-線程的建立方式
有可能有些人看不到這麼明顯的效果,這也很正常。所謂的多線程,指的是兩個線程的代碼可以同時運作,而不必一個線程需要等待另一個線程内的代碼執行完才可以運作。對于單核CPU來說,是無法做到真正的多線程的,每個時間點上,CPU都會執行特定的代碼,由于CPU執行代碼時間很快,是以兩個線程的代碼交替執行看起來像是同時執行的一樣。那具體執行某段代碼多少時間,就和分時機制系統有關了。分時系統把CPU時間劃分為多個時間片,作業系統以時間片為機關片為機關各個線程的代碼,越好的CPU分出的時間片越小。是以看不到明顯效果也很正常,一個線程列印5句話本來就很快,可能在分出的時間片内就執行完成了。是以,最簡單的解決辦法就是把for循環的值調大一點就可以了(也可以在for循環裡加Thread.sleep方法,這個之後再說)。
1.2 實作Runnable接口,重寫run方法
采用 java.lang.Runnable 也是非常常見的一種,我們隻需要重寫run方法即可。和繼承自Thread類差不多,不過實作Runnable後,還是要通過一個Thread來啟動:
- 定義Runnable接口的實作類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
-
建立Runnable實作類的執行個體,并以此執行個體作為Thread的target來建立Thread對象,該Thread對象才是真正
的線程對象。
- 調用線程對象的start()方法來啟動線程。
-
/** * @author bruceliu * @create 2019-05-30 11:37 * @description 實作Runnable接口,重寫run方法 */ public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } }
- 線程測試類
/** * @author bruceliu * @create 2019-05-30 11:39 * @description 實作Runnable接口,重寫run方法 */ public class TestMyRunnable { public static void main(String[] args) { //建立自定義類對象 線程任務對象 MyRunnable mr = new MyRunnable(); //建立線程對象 Thread t = new Thread(mr, "小強"); t.start(); for (int i = 0; i < 20; i++) { System.out.println("旺财 " + i); } } }
通過實作Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程式的一個執行目标。所有的多線程代碼都在run方法裡面。Thread類實際上也是實作了Runnable接口的類。
在啟動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然後調用Thread對象的start()方法來運作多線程代碼。
實際上所有的多線程代碼都是通過運作Thread的start()方法來運作的。是以,不管是繼承Thread類還是實作Runnable接口來實作多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程程式設計的基礎。
Runnable對象僅僅作為Thread對象的target,Runnable實作類裡包含的run()方法僅作為線程執行體。而實際的線程對象依然是Thread執行個體,隻是該Thread線程負責執行其target的run()方法。
1.3 使用匿名内部類方式
使用線程的内匿名内部類方式,可以友善的實作每個線程執行不同的線程任務操作。
使用匿名内部類的方式實作Runnable接口,重新Runnable接口中的run方法:
-
/** * @author bruceliu * @create 2019-05-30 11:55 * @description 使用匿名内部類方式 */ public class NoNameInnerClassThread { public static void main(String[] args) { System.out.println("-----多線程建立開始-----"); //‐‐‐這個整體 相當于new MyRunnable() Runnable r = new Runnable(){ public void run(){ for (int i = 0; i < 20; i++) { System.out.println("張宇:"+i); } } }; new Thread(r).start(); for (int i = 0; i < 20; i++) { System.out.println("費玉清:"+i); } System.out.println("-----多線程建立結束-----"); } }
1.4.Thread和Runnable的差別
如果一個類繼承Thread,則不适合資源共享。但是如果實作了Runable接口的話,則很容易的實作資源共享。
總結:
- 實作Runnable接口比繼承Thread類所具有的優勢:
- 适合多個相同的程式代碼的線程去共享同一個資源。
- 可以避免java中的單繼承的局限性。
- 增加程式的健壯性,實作解耦操作,代碼可以被多個線程共享,代碼和線程獨立。
- 線程池隻能放入實作Runable或Callable類線程,不能直接放入繼承Thread的類。
在java中,每次程式運作至少啟動2個線程。一個是main線程,一個是垃圾收集線程。因為每當使用
java指令執行一個類的時候,實際上都會啟動一個JVM,每一個JVM其實在就是在作業系統中啟動了一個程序。
2.Thread類
我們已經可以完成最基本的線程開啟,那麼在我們完成操作過程中用到了 java.lang.Thread 類,
API中該類中定義了有關線程的一些方法,具體如下:
- 構造方法:
:配置設定一個新的線程對象。public Thread()
:配置設定一個指定名字的新的線程對象。public Thread(String name)
:配置設定一個帶有指定目标新的線程對象。public Thread(Runnable target)
:配置設定一個帶有指定目标新的線程對象并指定名字。public Thread(Runnable target,String name)
- 常用方法方法:
:擷取目前線程名稱。public String getName()
:導緻此線程開始執行; Java虛拟機調用此線程的run方法。public void start()
:此線程要執行的任務在此處定義代碼。public void run()
:使目前正在執行的線程以指定的毫秒數暫停(暫時停止執行)。public static void sleep(long millis)
:傳回對目前正在執行的線程對象的引用public static Thread currentThread()
3.守護線程
在Java中有兩類線程:User Thread(使用者線程)、Daemon Thread(守護線程)
用個比較通俗的比如,任何一個守護線程都是整個JVM中所有非守護線程的保姆:隻要目前JVM執行個體中尚存在任何一個非守護線程沒有結束,守護線程就全部工作;隻有當最後一個非守護線程結束時,守護線程随着JVM一同結束工作。Daemon的作用是為其他線程的運作提供便利服務,守護線程最典型的應用就是 GC (垃圾回收器),它就是一個很稱職的守護者。
User和Daemon兩者幾乎沒有差別,唯一的不同之處就在于虛拟機的離開:如果 User Thread已經全部退出運作了,隻剩下Daemon Thread存在了,虛拟機也就退出了。 因為沒有了被守護者,Daemon也就沒有工作可做了,也就沒有繼續運作程式的必要了。
Thread daemonTread = new Thread();
// 設定 daemonThread 為 守護線程,default false(非守護線程)
daemonThread.setDaemon(true);
// 驗證目前線程是否為守護線程,傳回 true 則為守護線程
daemonThread.isDaemon();
/**
* @author bruceliu
* @create 2019-06-01 17:46
* @description 守護線程:程序線程(主線程挂了) 守護線程也會被自動銷毀.
*/
public class DaemonThread {
public static void main(String[] args) {
System.out.println("----------->主線程執行開始......");
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
System.out.println("我是子線程......");
}
}
});
//設定為守護線程
thread.setDaemon(true);
thread.start();
for (int i = 0; i <10 ; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
System.out.println("我是主線程...");
}
System.out.println("----------->主線程執行完畢......");
}
}