java线程的创建线程_多线程(Thread、线程创建、线程池)
第1章?多線程
1.1?多線程介紹
學(xué)習(xí)多線程之前,我們先要了解幾個關(guān)于多線程有關(guān)的概念。
進(jìn)程:進(jìn)程指正在運行的程序。確切的來說,當(dāng)一個程序進(jìn)入內(nèi)存運行,即變成一個進(jìn)程,進(jìn)程是處于運行過程中的程序,并且具有一定獨立功能。
線程:線程是進(jìn)程中的一個執(zhí)行單元,負(fù)責(zé)當(dāng)前進(jìn)程中程序的執(zhí)行,一個進(jìn)程中至少有一個線程。一個進(jìn)程中是可以有多個線程的,這個應(yīng)用程序也可以稱之為多線程程序。
簡而言之:一個程序運行后至少有一個進(jìn)程,一個進(jìn)程中可以包含多個線程
什么是多線程呢?即就是一個程序中有多個線程在同時執(zhí)行。
通過下圖來區(qū)別單線程程序與多線程程序的不同:
l?單線程程序:即,若有多個任務(wù)只能依次執(zhí)行。當(dāng)上一個任務(wù)執(zhí)行結(jié)束后,下一個任務(wù)開始執(zhí)行。如,去網(wǎng)吧上網(wǎng),網(wǎng)吧只能讓一個人上網(wǎng),當(dāng)這個人下機(jī)后,下一個人才能上網(wǎng)。
l?多線程程序:即,若有多個任務(wù)可以同時執(zhí)行。如,去網(wǎng)吧上網(wǎng),網(wǎng)吧能夠讓多個人同時上網(wǎng)。
1.2?程序運行原理
l?分時調(diào)度
所有線程輪流使用 CPU 的使用權(quán),平均分配每個線程占用CPU的時間。
l?搶占式調(diào)度
優(yōu)先讓優(yōu)先級高的線程使用 CPU,如果線程的優(yōu)先級相同,那么會隨機(jī)選擇一個(線程隨機(jī)性),Java使用的為搶占式調(diào)度。
1.2.1?搶占式調(diào)度詳解
大部分操作系統(tǒng)都支持多進(jìn)程并發(fā)運行,現(xiàn)在的操作系統(tǒng)幾乎都支持同時運行多個程序。比如:現(xiàn)在我們上課一邊使用編輯器,一邊使用錄屏軟件,同時還開著畫圖板,dos窗口等軟件。此時,這些程序是在同時運行,”感覺這些軟件好像在同一時刻運行著“。
實際上,CPU(中央處理器)使用搶占式調(diào)度模式在多個線程間進(jìn)行著高速的切換。對于CPU的一個核而言,某個時刻,只能執(zhí)行一個線程,而CPU的在多個線程間切換速度相對我們的感覺要快,看上去就是在同一時刻運行。
其實,多線程程序并不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。
1.3?主線程
回想我們以前學(xué)習(xí)中寫過的代碼,當(dāng)我們在dos命令行中輸入java空格類名回車后,啟動JVM,并且加載對應(yīng)的class文件。虛擬機(jī)并會從main方法開始執(zhí)行我們的程序代碼,一直把main方法的代碼執(zhí)行結(jié)束。如果在執(zhí)行過程遇到循環(huán)時間比較長的代碼,那么在循環(huán)之后的其他代碼是不會被馬上執(zhí)行的。如下代碼演示:
class Demo{
String name;
Demo(String name){
this.name = name;
}
void show() {
for (int i=1;i<=10000 ;i++ ) {
System.out.println("name="+name+",i="+i);
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d = new Demo("小強(qiáng)");
Demo d2 = new Demo("旺財");
d.show();
d2.show();
System.out.println("Hello World!");
}
}
若在上述代碼中show方法中的循環(huán)執(zhí)行次數(shù)很多,這時在d.show();下面的代碼是不會馬上執(zhí)行的,并且在dos窗口會看到不停的輸出name=小強(qiáng),i=值,這樣的語句。為什么會這樣呢?
原因是:jvm啟動后,必然有一個執(zhí)行路徑(線程)從main方法開始的,一直執(zhí)行到main方法結(jié)束,這個線程在java中稱之為主線程。當(dāng)程序的主線程執(zhí)行時,如果遇到了循環(huán)而導(dǎo)致程序在指定位置停留時間過長,則無法馬上執(zhí)行下面的程序,需要等待循環(huán)結(jié)束后能夠執(zhí)行。
那么,能否實現(xiàn)一個主線程負(fù)責(zé)執(zhí)行其中一個循環(huán),再由另一個線程負(fù)責(zé)其他代碼的執(zhí)行,最終實現(xiàn)多部分代碼同時執(zhí)行的效果?
能夠?qū)崿F(xiàn)同時執(zhí)行,通過Java中的多線程技術(shù)來解決該問題。
1.4?Thread類
該如何創(chuàng)建線程呢?通過API中搜索,查到Thread類。通過閱讀Thread類中的描述。Thread是程序中的執(zhí)行線程。Java 虛擬機(jī)允許應(yīng)用程序并發(fā)地運行多個執(zhí)行線程。
l?構(gòu)造方法
l?常用方法
繼續(xù)閱讀,發(fā)現(xiàn)創(chuàng)建新執(zhí)行線程有兩種方法。
l?一種方法是將類聲明為 Thread 的子類。該子類應(yīng)重寫 Thread 類的 run 方法。創(chuàng)建對象,開啟線程。run方法相當(dāng)于其他線程的main方法。
l?另一種方法是聲明一個實現(xiàn) Runnable 接口的類。該類然后實現(xiàn) run 方法。然后創(chuàng)建Runnable的子類對象,傳入到某個線程的構(gòu)造方法中,開啟線程。
1.5?創(chuàng)建線程方式一繼承Thread類
創(chuàng)建線程的步驟:
1 定義一個類繼承Thread。
2 重寫run方法。
3 創(chuàng)建子類對象,就是創(chuàng)建線程對象。
4 調(diào)用start方法,開啟線程并讓線程執(zhí)行,同時還會告訴jvm去調(diào)用run方法。
l?測試類
public class Demo01 {
public static void main(String[] args) {
//創(chuàng)建自定義線程對象
MyThread mt = new MyThread("新的線程!");
//開啟新線程
mt.start();
//在主方法中執(zhí)行for循環(huán)
for (int i = 0; i < 10; i++) {
System.out.println("main線程!"+i);
}
}
}
l?自定義線程類
public class MyThread extends Thread {
//定義指定線程名稱的構(gòu)造方法
public MyThread(String name) {
//調(diào)用父類的String參數(shù)的構(gòu)造方法,指定線程的名稱
super(name);
}
/**
* 重寫run方法,完成該線程執(zhí)行的邏輯
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在執(zhí)行!"+i);
}
}
}
思考:線程對象調(diào)用 run方法和調(diào)用start方法區(qū)別?
線程對象調(diào)用run方法不開啟線程。僅是對象調(diào)用方法。線程對象調(diào)用start開啟線程,并讓jvm調(diào)用run方法在開啟的線程中執(zhí)行。
1.5.1?繼承Thread類原理
我們?yōu)槭裁匆^承Thread類,并調(diào)用其的start方法才能開啟線程呢?
繼承Thread類:因為Thread類用來描述線程,具備線程應(yīng)該有功能。那為什么不直接創(chuàng)建Thread類的對象呢?如下代碼:
Thread t1 = new Thread();
t1.start();//這樣做沒有錯,但是該start調(diào)用的是Thread類中的run方法,而這個run方法沒有做什么事情,更重要的是這個run方法中并沒有定義我們需要讓線程執(zhí)行的代碼。
創(chuàng)建線程的目的是什么?
是為了建立程序單獨的執(zhí)行路徑,讓多部分代碼實現(xiàn)同時執(zhí)行。也就是說線程創(chuàng)建并執(zhí)行需要給定線程要執(zhí)行的任務(wù)。
對于之前所講的主線程,它的任務(wù)定義在main函數(shù)中。自定義線程需要執(zhí)行的任務(wù)都定義在run方法中。
Thread類run方法中的任務(wù)并不是我們所需要的,只有重寫這個run方法。既然Thread類已經(jīng)定義了線程任務(wù)的編寫位置(run方法),那么只要在編寫位置(run方法)中定義任務(wù)代碼即可。所以進(jìn)行了重寫run方法動作。
1.5.2?多線程的內(nèi)存圖解
多線程執(zhí)行時,到底在內(nèi)存中是如何運行的呢?
以上個程序為例,進(jìn)行圖解說明:
多線程執(zhí)行時,在棧內(nèi)存中,其實每一個執(zhí)行線程都有一片自己所屬的棧內(nèi)存空間。進(jìn)行方法的壓棧和彈棧。
當(dāng)執(zhí)行線程的任務(wù)結(jié)束了,線程自動在棧內(nèi)存中釋放了。但是當(dāng)所有的執(zhí)行線程都結(jié)束了,那么進(jìn)程就結(jié)束了。
1.5.3?獲取線程名稱
開啟的線程都會有自己的獨立運行棧內(nèi)存,那么這些運行的線程的名字是什么呢?該如何獲取呢?既然是線程的名字,按照面向?qū)ο蟮奶攸c,是哪個對象的屬性和誰的功能,那么我們就去找那個對象就可以了。查閱Thread類的API文檔發(fā)現(xiàn)有個方法是獲取當(dāng)前正在運行的線程對象。還有個方法是獲取當(dāng)前線程對象的名稱。既然找到了,我們就可以試試。
l?Thread.currentThread()獲取當(dāng)前線程對象
l?Thread.currentThread().getName();獲取當(dāng)前線程對象的名稱
class MyThread extends Thread { ?//繼承Thread
MyThread(String name){
super(name);
}
//復(fù)寫其中的run方法
public void run(){
for (int i=1;i<=20 ;i++ ){
System.out.println(Thread.currentThread().getName()+",i="+i);
}
}
}
class ThreadDemo {
public static void main(String[] args) {
//創(chuàng)建兩個線程任務(wù)
MyThread d = new MyThread();
MyThread d2 = new MyThread();
d.run();//沒有開啟新線程,在主線程調(diào)用run方法
d2.start();//開啟一個新線程,新線程調(diào)用run方法
}
}
通過結(jié)果觀察,原來主線程的名稱:main;自定義的線程:Thread-0,線程多個時,數(shù)字順延。如Thread-1......
進(jìn)行多線程編程時,不要忘記了Java程序運行是從主線程開始,main方法就是主線程的線程執(zhí)行內(nèi)容。
1.6?創(chuàng)建線程方式—實現(xiàn)Runnable接口
創(chuàng)建線程的另一種方法是聲明實現(xiàn) Runnable 接口的類。該類然后實現(xiàn)run方法。然后創(chuàng)建Runnable的子類對象,傳入到某個線程的構(gòu)造方法中,開啟線程。
為何要實現(xiàn)Runnable接口,Runable是啥玩意呢?繼續(xù)API搜索。
查看Runnable接口說明文檔:Runnable接口用來指定每個線程要執(zhí)行的任務(wù)。包含了一個run的無參數(shù)抽象方法,需要由接口實現(xiàn)類重寫該方法。
l?接口中的方法
l?Thread類構(gòu)造方法
創(chuàng)建線程的步驟。
1、定義類實現(xiàn)Runnable接口。
2、覆蓋接口中的run方法。。
3、創(chuàng)建Thread類的對象
4、將Runnable接口的子類對象作為參數(shù)傳遞給Thread類的構(gòu)造函數(shù)。
5、調(diào)用Thread類的start方法開啟線程。
l?代碼演示:
public class Demo02 {
public static void main(String[] args) {
//創(chuàng)建線程執(zhí)行目標(biāo)類對象
Runnable runn?= new MyRunnable();
//將Runnable接口的子類對象作為參數(shù)傳遞給Thread類的構(gòu)造函數(shù)
Thread thread = new Thread(runn);
Thread thread2 = new Thread(runn);
//開啟線程
thread.start();
thread2.start();
for (int i = 0; i < 10; i++) {
System.out.println("main線程:正在執(zhí)行!"+i);
}
}
}
l?自定義線程執(zhí)行任務(wù)類
public class MyRunnable implements Runnable{
//定義線程要執(zhí)行的run方法邏輯
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我的線程:正在執(zhí)行!"+i);
}
}
}
1.6.1?實現(xiàn)Runnable的原理
為什么需要定一個類去實現(xiàn)Runnable接口呢?繼承Thread類和實現(xiàn)Runnable接口有啥區(qū)別呢?
實現(xiàn)Runnable接口,避免了繼承Thread類的單繼承局限性。覆蓋Runnable接口中的run方法,將線程任務(wù)代碼定義到run方法中。
創(chuàng)建Thread類的對象,只有創(chuàng)建Thread類的對象才可以創(chuàng)建線程。線程任務(wù)已被封裝到Runnable接口的run方法中,而這個run方法所屬于Runnable接口的子類對象,所以將這個子類對象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù),這樣,線程對象創(chuàng)建時就可以明確要運行的線程的任務(wù)。
1.6.2?實現(xiàn)Runnable的好處
第二種方式實現(xiàn)Runnable接口避免了單繼承的局限性,所以較為常用。實現(xiàn)Runnable接口的方式,更加的符合面向?qū)ο?#xff0c;線程分為兩部分,一部分線程對象,一部分線程任務(wù)。繼承Thread類,線程對象和線程任務(wù)耦合在一起。一旦創(chuàng)建Thread類的子類對象,既是線程對象,有又有線程任務(wù)。實現(xiàn)runnable接口,將線程任務(wù)單獨分離出來封裝成對象,類型就是Runnable接口類型。Runnable接口對線程對象和線程任務(wù)進(jìn)行解耦。
1.7?線程的匿名內(nèi)部類使用
使用線程的內(nèi)匿名內(nèi)部類方式,可以方便的實現(xiàn)每個線程執(zhí)行不同的線程任務(wù)操作。
l?方式1:創(chuàng)建線程對象時,直接重寫Thread類中的run方法
new?Thread() {
public?void?run() {
for?(int?x = 0; x < 40; x++) {
System.out.println(Thread.currentThread().getName()
+ "...X...."?+ x);
}
}
}.start();
l?方式2:使用匿名內(nèi)部類的方式實現(xiàn)Runnable接口,重新Runnable接口中的run方法
Runnable r = new?Runnable() {
public?void?run() {
for?(int?x = 0; x < 40; x++) {
System.out.println(Thread.currentThread().getName()
+ "...Y...."?+ x);
}
}
};
new?Thread(r).start();
第2章?線程池
2.1?線程池概念
線程池,其實就是一個容納多個線程的容器,其中的線程可以反復(fù)使用,省去了頻繁創(chuàng)建線程對象的操作,無需反復(fù)創(chuàng)建線程而消耗過多資源。
我們詳細(xì)的解釋一下為什么要使用線程池?
在java中,如果每個請求到達(dá)就創(chuàng)建一個新線程,開銷是相當(dāng)大的。在實際使用中,創(chuàng)建和銷毀線程花費的時間和消耗的系統(tǒng)資源都相當(dāng)大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。除了創(chuàng)建和銷毀線程的開銷之外,活動的線程也需要消耗系統(tǒng)資源。如果在一個jvm里創(chuàng)建太多的線程,可能會使系統(tǒng)由于過度消耗內(nèi)存或“切換過度”而導(dǎo)致系統(tǒng)資源不足。為了防止資源不足,需要采取一些辦法來限制任何給定時刻處理的請求數(shù)目,盡可能減少創(chuàng)建和銷毀線程的次數(shù),特別是一些資源耗費比較大的線程的創(chuàng)建和銷毀,盡量利用已有對象來進(jìn)行服務(wù)。
線程池主要用來解決線程生命周期開銷問題和資源不足問題。通過對多個任務(wù)重復(fù)使用線程,線程創(chuàng)建的開銷就被分?jǐn)偟搅硕鄠€任務(wù)上了,而且由于在請求到達(dá)時線程已經(jīng)存在,所以消除了線程創(chuàng)建所帶來的延遲。這樣,就可以立即為請求服務(wù),使用應(yīng)用程序響應(yīng)更快。另外,通過適當(dāng)?shù)恼{(diào)整線程中的線程數(shù)目可以防止出現(xiàn)資源不足的情況。
2.2?使用線程池方式--Runnable接口
通常,線程池都是通過線程池工廠創(chuàng)建,再調(diào)用線程池中的方法獲取線程,再通過線程去執(zhí)行任務(wù)方法。
l?Executors:線程池創(chuàng)建工廠類
l?public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象
l?ExecutorService:線程池類
l?Future> submit(Runnable?task):獲取線程池中的某一個線程對象,并執(zhí)行
l?Future接口:用來記錄線程任務(wù)執(zhí)行完畢后產(chǎn)生的結(jié)果。線程池創(chuàng)建與使用
l?使用線程池中線程對象的步驟:
l?創(chuàng)建線程池對象
l?創(chuàng)建Runnable接口子類對象
l?提交Runnable接口子類對象
l?關(guān)閉線程池
代碼演示:
public class ThreadPoolDemo {
public static void main(String[] args) {
//創(chuàng)建線程池對象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
//創(chuàng)建Runnable實例對象
MyRunnable r = new MyRunnable();
//自己創(chuàng)建線程對象的方式
//Thread t = new Thread(r);
//t.start(); ---> 調(diào)用MyRunnable中的run()
//從線程池中獲取線程對象,然后調(diào)用MyRunnable中的run()
service.submit(r);
//再獲取個線程對象,調(diào)用MyRunnable中的run()
service.submit(r);
service.submit(r);
//注意:submit方法調(diào)用結(jié)束后,程序并不終止,是因為線程池控制了線程的關(guān)閉。將使用完的線程又歸還到了線程池中
//關(guān)閉線程池
//service.shutdown();
}
}
l?Runnable接口實現(xiàn)類
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一個教練");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教練來了:" +Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教練回到了游泳池");
}
}
2.3?使用線程池方式—Callable接口
l?Callable接口:與Runnable接口功能相似,用來指定線程的任務(wù)。其中的call()方法,用來返回線程任務(wù)執(zhí)行完畢后的結(jié)果,call方法可拋出異常。
l?ExecutorService:線程池類
l? Future submit(Callable?task):獲取線程池中的某一個線程對象,并執(zhí)行線程中的call()方法
l?Future接口:用來記錄線程任務(wù)執(zhí)行完畢后產(chǎn)生的結(jié)果。線程池創(chuàng)建與使用
l?使用線程池中線程對象的步驟:
l?創(chuàng)建線程池對象
l?創(chuàng)建Callable接口子類對象
l?提交Callable接口子類對象
l?關(guān)閉線程池
代碼演示:
public class ThreadPoolDemo {
public static void main(String[] args) {
//創(chuàng)建線程池對象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
//創(chuàng)建Callable對象
MyCallable c = new MyCallable();
//從線程池中獲取線程對象,然后調(diào)用MyRunnable中的run()
service.submit(c);
//再獲取個教練
service.submit(c);
service.submit(c);
//注意:submit方法調(diào)用結(jié)束后,程序并不終止,是因為線程池控制了線程的關(guān)閉。將使用完的線程又歸還到了線程池中
//關(guān)閉線程池
//service.shutdown();
}
}
l?Callable接口實現(xiàn)類,call方法可拋出異常、返回線程任務(wù)執(zhí)行完畢后的結(jié)果
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("我要一個教練:call");
Thread.sleep(2000);
System.out.println("教練來了:" +Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教練回到了游泳池");
return null;
}
}
2.4?線程池練習(xí):返回兩個數(shù)相加的結(jié)果
要求:通過線程池中的線程對象,使用Callable接口完成兩個數(shù)求和操作
l?Future接口:用來記錄線程任務(wù)執(zhí)行完畢后產(chǎn)生的結(jié)果。線程池創(chuàng)建與使用
l?V get() 獲取Future對象中封裝的數(shù)據(jù)結(jié)果
代碼演示:
public class ThreadPoolDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//創(chuàng)建線程池對象
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//創(chuàng)建一個Callable接口子類對象
//MyCallable c = new MyCallable();
MyCallable c = new MyCallable(100, 200);
MyCallable c2 = new MyCallable(10, 20);
//獲取線程池中的線程,調(diào)用Callable接口子類對象中的call()方法,完成求和操作
// Future submit(Callable task)
// Future 結(jié)果對象
Future result = threadPool.submit(c);
//此Future的get方法所返回的結(jié)果類型
Integer sum = result.get();
System.out.println("sum=" + sum);
//再演示
result = threadPool.submit(c2);
sum = result.get();
System.out.println("sum=" + sum);
//關(guān)閉線程池(可以不關(guān)閉)
}
}
l?Callable接口實現(xiàn)類
public class MyCallable implements Callable {
//成員變量
int x = 5;
int y = 3;
//構(gòu)造方法
public MyCallable(){
}
public MyCallable(int x, int y){
this.x = x;
this.y = y;
}
@Override
public Integer call() throws Exception {
return x+y;
}
}
第3章?總結(jié)
3.1?知識點總結(jié)
l?創(chuàng)建線程的方式
l?方式1,繼承Thread線程類
l?步驟
1,?自定義類繼承Thread類
2,?在自定義類中重寫Thread類的run方法
3,?創(chuàng)建自定義類對象(線程對象)
4,?調(diào)用start方法,啟動線程,通過JVM,調(diào)用線程中的run方法
l?方式2,實現(xiàn)Runnable接口
l?步驟
1,?創(chuàng)建線程任務(wù)類 實現(xiàn)Runnable接口
2,?在線程任務(wù)類中 重寫接口中的run方法
3,?創(chuàng)建線程任務(wù)類對象
4,?創(chuàng)建線程對象,把線程任務(wù)類對象作為Thread類構(gòu)造方法的參數(shù)使用
5,?調(diào)用start方法,啟動線程,通過JVM,調(diào)用線程任務(wù)類中的run方法
總結(jié)
以上是生活随笔為你收集整理的java线程的创建线程_多线程(Thread、线程创建、线程池)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么生成自己的网址(怎么生成自己的网址链
- 下一篇: 安卓过期文件怎么恢复微信(安卓过期)