Java多線程基本用法總結(jié)
這篇是Java多線程基本用法的一個(gè)總結(jié)。
本篇文章會(huì)從一下幾個(gè)方面來(lái)說(shuō)明Java多線程的基本用法:
- 如何使用多線程
- 如何得到多線程的一些信息
- 如何停止線程
- 如何暫停線程
- 線程的一些其他用法
如何使用多線程
啟動(dòng)線程的兩種方式
Java 提供了2種方式來(lái)使用多線程, 一種是編寫一個(gè)類來(lái)繼承Thread,然后覆寫run方法,然后調(diào)用start方法來(lái)啟動(dòng)線程。這時(shí)這個(gè)類就會(huì)以另一個(gè)線程的方式來(lái)運(yùn)行run方法里面的代碼。另一種是編寫一個(gè)類來(lái)實(shí)現(xiàn)Runnable接口,然后實(shí)現(xiàn)接口方法run,然后創(chuàng)造一個(gè)Thread對(duì)象,把實(shí)現(xiàn)了Runnable接口的類當(dāng)做構(gòu)造參數(shù),傳入Thread對(duì)象,最后該Thread對(duì)象調(diào)用start方法。
這里的start方法是一個(gè)有啟動(dòng)功能的方法,該方法內(nèi)部回調(diào)run方法。所以,只有調(diào)用了start方法才會(huì)啟動(dòng)另一個(gè)線程,直接調(diào)用run方法,還是在同一個(gè)線程中執(zhí)行run,而不是在另一個(gè)線程執(zhí)行run
此外,start方法只是告訴虛擬機(jī),該線程可以啟動(dòng)了,也就說(shuō)該線程在就緒的狀態(tài),但不代表調(diào)用start就立即運(yùn)行了,這要等待JVM來(lái)決定什么時(shí)候執(zhí)行這個(gè)線程。也就是說(shuō),如果有兩個(gè)線程A,B ,A先調(diào)用start,B后調(diào)用start,不代表A線程先運(yùn)行,B線程后運(yùn)行。這都是由JVM決定了,可以認(rèn)為是隨機(jī)啟動(dòng)。
下面我們用實(shí)際的代碼,來(lái)說(shuō)明兩種啟動(dòng)線程的方式:
第一種,繼承Thread
public class ExampleThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("這是一個(gè)繼承自Thread的ExampleThread");
}
}
測(cè)試的代碼可以看test目錄下的ExampleThreadTest類
另一種,實(shí)現(xiàn)了Runnable接口
public class ExampleRunable implements Runnable{
public void run() {
System.out.println("這是實(shí)現(xiàn)Runnable接口的類");
}
}
測(cè)試的代碼可以看test目錄下的ExampleRunableTest類。
如何得到多線程的一些信息
我們?cè)趩?dòng)多線程之后,希望能通過(guò)一些API得到啟動(dòng)的線程的一些信息。JDK給我們提供了一個(gè)Thread類的方法來(lái)得到線程的一些信息。
- 線程的名字 —— getName()
- 線程的ID —— getId()
- 線程是否存活 —— isAlive()
得到線程的名字
這些方法是屬于Thread的內(nèi)部方法,所以我們可以用兩種方式調(diào)用這些方法,一個(gè)是我們的類繼承Thread來(lái)使用多線程的時(shí)候,可以用過(guò)this來(lái)調(diào)用。另一種是通過(guò)Thread.currentThread() 來(lái)調(diào)用這些方法。但是這兩個(gè)方法在不同的使用場(chǎng)景下是有區(qū)別的。
我們先簡(jiǎn)單來(lái)看兩個(gè)方法的使用。
第一個(gè)Thread.currentThread()的使用,代碼如下:
public class ExampleCurrentThread extends Thread{
public ExampleCurrentThread(){
System.out.println("構(gòu)造方法的打?。? + Thread.currentThread().getName());
}
@Override
public void run() {
super.run();
System.out.println("run方法的打?。? + Thread.currentThread().getName());
}
}
測(cè)試的代碼如下:
public class ExampleCurrentThreadTest extends TestCase {
public void testInit() throws Exception{
ExampleCurrentThread thread = new ExampleCurrentThread();
}
public void testRun() throws Exception {
ExampleCurrentThread thread = new ExampleCurrentThread();
thread.start();
Thread.sleep(1000);
}
}
結(jié)果如下:
構(gòu)造方法的打?。簃ain run方法的打?。篢hread-0 構(gòu)造方法的打印:main
為什么我們?cè)贓xampleCurrentThread內(nèi)部用Thread.currentThread()會(huì)顯示構(gòu)造方法的打印是main,是因?yàn)門hread.currentThread()返回的是代碼段正在被那個(gè)線程調(diào)用的信息。這里面很顯然構(gòu)造方法是被main線程執(zhí)行的,而run方法是被我們自己?jiǎn)?dòng)的線程執(zhí)行的,因?yàn)闆](méi)有給他起名字,所以默認(rèn)是Thread-0。
接下來(lái),我們?cè)诳匆豢蠢^承自Thread,用this調(diào)用。
public class ComplexCurrentThread extends Thread{
public ComplexCurrentThread() {
System.out.println("begin=========");
System.out.println("Thread.currentThread().getName=" + Thread.currentThread().getName());
System.out.println("this.getName()=" + this.getName());
System.out.println("end===========");
}
@Override
public void run() {
super.run();
System.out.println("run begin=======");
System.out.println("Thread.currentThread().getName=" + Thread.currentThread().getName());
System.out.println("this.getName()=" + this.getName());
System.out.println("run end==========");
}
}
測(cè)試代碼如下:
public class ComplexCurrentThreadTest extends TestCase {
public void testRun() throws Exception {
ComplexCurrentThread thread = new ComplexCurrentThread();
thread.setName("byhieg");
thread.start();
Thread.sleep(3000);
}
}
結(jié)果如下:
begin========= Thread.currentThread().getName=main this.getName()=Thread-0 end=========== run begin======= Thread.currentThread().getName=byhieg this.getName()=byhieg run end==========
首先在創(chuàng)建對(duì)象的時(shí)候,構(gòu)造器還是被main線程所執(zhí)行,所以Thread.currentThread()得到的就是Main線程的名字,但是this方法指的是調(diào)用方法的那個(gè)對(duì)象,也就是ComplexCurrentThread的線程信息,還沒(méi)有setName,所以是默認(rèn)的名字。然后run方法無(wú)論是Thread.currentThread()還是this返回的都是設(shè)置了byhieg名字的線程信息。
所以Thread.currentThread指的是具體執(zhí)行這個(gè)代碼塊的線程信息。構(gòu)造器是main執(zhí)行的,而run方法則是哪個(gè)線程start,哪個(gè)線程執(zhí)行run。這么看來(lái),this能得到的信息是不準(zhǔn)確的,因?yàn)槿绻覀冊(cè)趓un中執(zhí)行了this.getName(),但是run方法卻是由另一個(gè)線程start的,我們是無(wú)法通過(guò)this.getName得到運(yùn)行run方法的新城的信息的。而且只有繼承了Thread的類才能有g(shù)etName等方法,這對(duì)于Java沒(méi)有多繼承的特性語(yǔ)言來(lái)說(shuō),是個(gè)災(zāi)難。所有后面凡是要得到線程的信息,我們都用Thread.currentThread()來(lái)調(diào)用API。
得到線程的ID
調(diào)用getID取得線程的唯一標(biāo)識(shí)。這個(gè)和上面的getName用法一致,沒(méi)什么好說(shuō)的,可以直接看ExampleIdThread和他的測(cè)試類ExampleIdThreadTest。
判斷線程是否存活
方法isAlive()的作用是測(cè)試線程是否處于活動(dòng)狀態(tài)。所謂活動(dòng)狀態(tài),就是線程已經(jīng)啟動(dòng)但是沒(méi)有終止。即該線程start之后,被認(rèn)為是存活的。
我們看一下具體的例子:
public class AliveThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("run方法中是否存活" + " " + Thread.currentThread().isAlive());
}
}
測(cè)試方法如下:
public class AliveThreadTest extends TestCase {
public void testRun() throws Exception {
AliveThread thread = new AliveThread();
System.out.println("begin == " + thread.isAlive());
thread.start();
Thread.sleep(1000);
System.out.println("end ==" + thread.isAlive());
Thread.sleep(3000);
}
}
結(jié)果如下:
begin == false run方法中是否存活 true end ==false
我們可以發(fā)現(xiàn)在start之前,該線程被認(rèn)為是沒(méi)有存活,然后run的時(shí)候,是存活的,等run方法執(zhí)行完,又被認(rèn)為是不存活的。
如何停止線程
判斷線程是否終止
JDK提供了一些方法來(lái)判斷線程是否終止 —— isInterrupted()和interrupted()
停止線程的方式
這個(gè)是得到線程信息中比較重要的一個(gè)方法了,因?yàn)檫@個(gè)和終止線程的方法相關(guān)聯(lián)。先說(shuō)一下終止線程的幾種方式:
- 等待run方法執(zhí)行完
- 線程對(duì)象調(diào)用stop()
- 線程對(duì)象調(diào)用interrupt(),在該線程的run方法中判斷是否終止,拋出一個(gè)終止異常終止。
- 線程對(duì)象調(diào)用interrupt(),在該線程的run方法中判斷是否終止,以return語(yǔ)句結(jié)束。
第一種就不說(shuō)了,第二種stop()方法已經(jīng)廢棄了,因?yàn)榭赡軙?huì)產(chǎn)生如下原因:
- 強(qiáng)制結(jié)束線程,該線程應(yīng)該做的清理工作,無(wú)法完成。
- 強(qiáng)制結(jié)束線程,該線程已操作的加鎖對(duì)象強(qiáng)制解鎖,造成數(shù)據(jù)不一致。
具體的例子可以看StopLockThread以及他的測(cè)試類StopLockThreadTest
第三種,是目前推薦的終止方法,調(diào)用interrupt,然后在run方法中判斷是否終止。判斷終止的方式有兩種,一種是Thread類的靜態(tài)方法interrupted(),另一種是Thread的成員方法isInterrupted()。這兩個(gè)方法是有所區(qū)別的,第一個(gè)方法是會(huì)自動(dòng)重置狀態(tài)的,如果連續(xù)兩次調(diào)用interrupted(),第一次如果是false,第二次一定是true。而isInterrupted()是不會(huì)的。
例子如下:
public class ExampleInterruptThread extends Thread{
@Override
public void run() {
super.run();
try{
for(int i = 0 ; i < 50000000 ; i++){
if (interrupted()){
System.out.println("已經(jīng)是停止?fàn)顟B(tài),我要退出了");
throw new InterruptedException("停止.......");
}
System.out.println("i=" + (i + 1));
}
}catch (InterruptedException e){
System.out.println("順利停止");
}
}
}
測(cè)試的代碼如下:
public class ExampleInterruptThreadTest extends TestCase {
public void testRun() throws Exception {
ExampleInterruptThread thread = new ExampleInterruptThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
第四種方法和第三種一樣,唯一的區(qū)別就是將上面的代碼中的拋出異常換成return,個(gè)人還是喜歡拋出異常,這里處理的形式就比較多,比如打印信息,處理資源關(guān)閉或者捕捉之后再重新向上層拋出。
注意一點(diǎn),我們上面拋出的異常是InterruptedException,這里簡(jiǎn)單說(shuō)一下可能產(chǎn)生這個(gè)異常的原因,在原有線程sleep的情況下,調(diào)用interrupt終止線程,或者先終止線程,再讓線程sleep。
如何暫停線程
在JDK中提供了以下兩個(gè)方法用來(lái)暫停線程和恢復(fù)線程。
- suspend()——暫停線程
- resume()——恢復(fù)線程
這兩個(gè)方法和stop方法一樣是被廢棄的方法,其用法和stop一樣,暴力的暫停線程和恢復(fù)線程。這兩個(gè)方法之所以是廢棄的主要由以下兩個(gè)原因:
- 線程持有鎖定的公共資源的情況下,一旦被暫停,則公共資源無(wú)法被其他線程所持有。
- 線程強(qiáng)制暫停,導(dǎo)致該線程執(zhí)行的操作沒(méi)有執(zhí)行完全,這時(shí)訪問(wèn)該線程的數(shù)據(jù)會(huì)出現(xiàn)數(shù)據(jù)不一致。
線程的一些其他用法
線程的其他的一些基礎(chǔ)用法如下:
- 線程讓步
- 設(shè)置線程的優(yōu)先級(jí)
- 守護(hù)線程
線程讓步
JDK提供yield()方法來(lái)讓線程放棄當(dāng)前的CPU資源,將它讓給其他的任務(wù)去占用CPU時(shí)間,但是這也是隨機(jī)的事情,有可能剛放棄資源,又馬上占用時(shí)間片了。
具體的例子可以參考ExampleYieldThread以及他的測(cè)試類ExampleYieldThreadTest
設(shè)置線程的優(yōu)先級(jí)
我們可以設(shè)置線程的優(yōu)先級(jí)來(lái)讓CPU盡可能的將執(zhí)行的資源給優(yōu)先級(jí)高的線程。Java設(shè)置了1-10這10個(gè)優(yōu)先級(jí),又有三個(gè)靜態(tài)變量來(lái)提供三個(gè)優(yōu)先級(jí):
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
我們可以通過(guò)setPriority來(lái)設(shè)置線程的優(yōu)先級(jí),可以直接傳入上訴三個(gè)靜態(tài)變量,也可以直接傳入1-10的數(shù)字。設(shè)置后線程就會(huì)有不同的優(yōu)先級(jí)。如果我們不設(shè)置優(yōu)先級(jí),會(huì)是什么情況?
線程的優(yōu)先級(jí)是有繼承的特性,如果我們?cè)贏線程中啟動(dòng)了B線程,則AB具有相同的優(yōu)先級(jí)。一般我們?cè)趍ain線程中啟動(dòng)線程,就和main線程有一致的優(yōu)先級(jí)。main線程的優(yōu)先級(jí)默認(rèn)是5。
下面說(shuō)一下優(yōu)先級(jí)的一些規(guī)則:
- 優(yōu)先級(jí)高的線程一般會(huì)比優(yōu)先級(jí)低的線程獲得更多的CPU資源,但是不代表優(yōu)先級(jí)高的任務(wù)一定先于優(yōu)先級(jí)低的任務(wù)先執(zhí)行完。因?yàn)椴煌瑑?yōu)先級(jí)的線程中run方法內(nèi)容可能不一樣。
- 優(yōu)先級(jí)高的線程一定會(huì)比優(yōu)先級(jí)低的線程執(zhí)行的快。如果兩個(gè)線程是一樣的run方法,但是優(yōu)先級(jí)不一樣,確實(shí)優(yōu)先級(jí)高的線程先執(zhí)行完。
線程守護(hù)
JDK中提供setDaemon的方法來(lái)設(shè)置一個(gè)線程變成守護(hù)線程。守護(hù)線程的特點(diǎn)是其他非守護(hù)線程執(zhí)行完,守護(hù)線程就自動(dòng)銷毀,典型的例子是GC回收器。
具體可以看ExampleDaemonThread和ExampleDaemonThreadTest。
總結(jié)
這篇文章主要總結(jié)了Java線程的一些基本的用法,關(guān)于線程安全,同步的知識(shí),放到了第二篇。
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
Spring Cloud負(fù)載均衡及遠(yuǎn)程調(diào)用實(shí)現(xiàn)詳解
這篇文章主要介紹了Spring Cloud負(fù)載均衡及遠(yuǎn)程調(diào)用實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
springboot整合httpClient代碼實(shí)例
這篇文章主要介紹了springboot整合httpClient代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Kotlin基礎(chǔ)教程之dataclass,objectclass,use函數(shù),類擴(kuò)展,socket
這篇文章主要介紹了Kotlin基礎(chǔ)教程之dataclass,objectclass,use函數(shù),類擴(kuò)展,socket的相關(guān)資料,需要的朋友可以參考下2017-05-05
Feign遠(yuǎn)程調(diào)用丟失請(qǐng)求頭問(wèn)題
本文介紹了在服務(wù)端項(xiàng)目中如何解決資源訪問(wèn)限制問(wèn)題,首先介紹了問(wèn)題的產(chǎn)生,然后詳細(xì)解析了源碼,最后提出了解決方案,解決方案包括同步和異步兩種,同步時(shí)直接向Spring容器注入RequestInterceptor攔截器2024-09-09
Java工具jsch.jar實(shí)現(xiàn)上傳下載
這篇文章主要為大家詳細(xì)介紹了Java操作ftp的一款工具,利用jsch.jar針對(duì)sftp的上傳下載工具類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Java如何使用JSR303校驗(yàn)數(shù)據(jù)與自定義校驗(yàn)注解
這篇文章主要介紹了Java如何使用JSR303校驗(yàn)數(shù)據(jù)與自定義校驗(yàn)注解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Java使用easypoi快速導(dǎo)入導(dǎo)出的實(shí)現(xiàn)
這篇文章主要介紹了實(shí)現(xiàn)Java使用easypoi快速導(dǎo)入導(dǎo)出的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03
Dubbo?retries?超時(shí)重試機(jī)制的問(wèn)題原因分析及解決方案
這篇文章主要介紹了Dubbo?retries?超時(shí)重試機(jī)制的問(wèn)題,解決方案是通過(guò)修改dubbo服務(wù)提供方,將timeout超時(shí)設(shè)為20000ms或者設(shè)置retries=“0”,禁用超時(shí)重試機(jī)制,感興趣的朋友跟隨小編一起看看吧2022-04-04

