Java線程創(chuàng)建與Thread類的使用方法
1.線程與Thread類
1.1操作系統(tǒng)中的線程與Java線程
1.1.1線程與Thread類
線程是操作系統(tǒng)中的概念. 操作系統(tǒng)內(nèi)核實(shí)現(xiàn)了線程這樣的機(jī)制, 并且對(duì)用戶層提供了一些 API 供用戶使用(例如 Linux 的 pthread 庫(kù)).
Java 標(biāo)準(zhǔn)庫(kù)中 Thread 類可以視為是對(duì)操作系統(tǒng)提供的 API 進(jìn)行了進(jìn)一步的抽象和封裝. 也就是說(shuō)Thread
類的一個(gè)實(shí)例就對(duì)應(yīng)著一個(gè)線程。
1.1.2Thread類的構(gòu)造方法
序號(hào) | 方法名 | 解釋 |
---|---|---|
1 | public Thread() | 無(wú)參數(shù)構(gòu)造方法 |
2 | public Thread(Runnable target) | 傳入實(shí)現(xiàn)Runnable接口的對(duì)象(任務(wù)對(duì)象)構(gòu)造線程 |
3 | public Thread(Runnable target, String name) | 根據(jù)目標(biāo)任務(wù)并指定線程名創(chuàng)建線程 |
4 | public Thread(ThreadGroup group, Runnable target) | 根據(jù)線程組和任務(wù)創(chuàng)建線程(了解) |
5 | public Thread(ThreadGroup group, Runnable target, String name) | 比構(gòu)造方法4多一個(gè)指定線程名 |
6 | public Thread(String name) | 指定線程名創(chuàng)建線程 |
7 | public Thread(ThreadGroup group, String name) | 根據(jù)線程組并指定線程名創(chuàng)建線程 |
8 | public Thread(ThreadGroup group, Runnable target, String name,long stackSize) | 構(gòu)造函數(shù)與構(gòu)造方法5相同,只是它允許指定線程堆棧大小 |
注:線程可以被用來(lái)分組管理,分好的組即為線程組,Runnable
類表示任務(wù)類,也就是線程需執(zhí)行的任務(wù)。
1.1.3啟用java線程必會(huì)的方法
想要使用java線程至少得知道Thread類中這幾個(gè)方法:
方法名 | 解釋 |
---|---|
public void run() | 該方法用來(lái)封裝線程運(yùn)行時(shí)執(zhí)行的內(nèi)容 |
public synchronized void start() | 線程創(chuàng)建并執(zhí)行run方法 |
public static native void sleep(long millis) throws InterruptedException | 使線程休眠millis毫秒 |
創(chuàng)建Thread
對(duì)象,必須重寫run
方法,因?yàn)槟銊?chuàng)建一個(gè)線程肯定要用運(yùn)行一些代碼嘛。
1.2第一個(gè)Java多線程程序
首先,我們可以創(chuàng)建一個(gè)MyThread
類繼承Thread
類,并重寫run
方法。
class MyThread extends Thread{ //重寫run方法 @Override public void run() { System.out.println("你好!線程!"); } } public class TestDemo { public static void main(String[] args) { //創(chuàng)建MyThread線程對(duì)象,但是線程沒(méi)有創(chuàng)建 Thread thread = new MyThread(); //線程創(chuàng)建并運(yùn)行 thread.start(); } }
使用new
創(chuàng)建線程對(duì)象,線程并沒(méi)有被創(chuàng)建,僅僅只是單純地創(chuàng)建了一個(gè)線程對(duì)象,運(yùn)行start
方法時(shí)才會(huì)創(chuàng)建線程并執(zhí)行run
方法。
運(yùn)行結(jié)果:
1.3使用Runnable對(duì)象創(chuàng)建線程
除了使用子類繼承Thread
類并重寫run
方法,使用子類實(shí)現(xiàn)Runnable
接口(該接口中也有一個(gè)run
方法,表示任務(wù)的內(nèi)容),該對(duì)象可以理解為“任務(wù)”,也就是說(shuō)Thread
對(duì)象可以接受Runnable
引用,并執(zhí)行Runnable
引用的run
方法。
因?yàn)?code>Runable是一個(gè)接口,所以需要實(shí)現(xiàn)run
方法,線程Thread
對(duì)象創(chuàng)建好后,此時(shí)線程并沒(méi)有創(chuàng)建運(yùn)行,需要調(diào)用start
方法來(lái)創(chuàng)建啟動(dòng)線程。
class MyRunnable implements Runnable { @Override public void run() { System.out.println("使用Runnable描述任務(wù)!"); } } public class TestDemo3 { public static void main(String[] args) { //將Runnable任務(wù)傳給Thread對(duì)象來(lái)創(chuàng)建運(yùn)行線程 Runnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } }
運(yùn)行結(jié)果:
根據(jù)“低內(nèi)聚,高耦合”的
編程風(fēng)格,使用Runnable
的方式創(chuàng)建更優(yōu)。
1.4使用內(nèi)部類創(chuàng)建線程
當(dāng)然也可以使用匿名內(nèi)部類,來(lái)傳入匿名對(duì)象來(lái)重寫run
方法。
public class TestDemo4 { public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { System.out.println("使用匿名內(nèi)部類創(chuàng)建線程匿名對(duì)象"); } }; thread.start(); } }
運(yùn)行結(jié)果:
1.5使用Lambda表達(dá)式創(chuàng)建線程
使用Lambda表達(dá)式,本質(zhì)還是使用匿名內(nèi)部類創(chuàng)建的Thread
。
public class TestDemo6 { public static void main(String[] args) { Thread thread = new Thread(() -> System.out.println("使用Lambda表達(dá)式表示匿名內(nèi)部類來(lái)創(chuàng)建匿名任務(wù)")); thread.start(); } }
運(yùn)行結(jié)果:
1.6多線程并發(fā)執(zhí)行簡(jiǎn)單演示
在一個(gè)進(jìn)程中至少會(huì)有一個(gè)線程,如果不使用多線程編程,一個(gè)進(jìn)程中默認(rèn)會(huì)有執(zhí)行main
方法的main
線程(該線程是自動(dòng)創(chuàng)建的),當(dāng)你創(chuàng)建一個(gè)新的線程t
,該線程會(huì)與main
線程并發(fā)執(zhí)行。
public class TestDemo7 { public static void main(String[] args) { //thread 線程 Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("thread線程執(zhí)行中!"); //為了使效果更加明顯 可以使用sleep方法設(shè)定線程睡眠時(shí)間 try { Thread.sleep(1000);//每執(zhí)行一次循環(huán)就睡眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }); thread.start(); //main 線程 for (int i = 0; i < 10; i++) { System.out.println("main線程執(zhí)行中!"); //為了使效果更加明顯 可以使用sleep方法設(shè)定線程睡眠時(shí)間 try { Thread.sleep(1000);//每執(zhí)行一次循環(huán)就睡眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
運(yùn)行結(jié)果:
從上面的運(yùn)行結(jié)果可以看出一個(gè)問(wèn)題,因?yàn)?code>thread線程與main
線程都是每打印一句語(yǔ)句線程休眠1
秒,兩個(gè)線程喚醒的先后順序是隨機(jī)的,這也是java多線程中的一個(gè)“萬(wàn)惡之源”,這個(gè)問(wèn)題給我們帶來(lái)了很多麻煩,細(xì)節(jié)等后續(xù)的博客細(xì)說(shuō)。
1.7多線程并發(fā)執(zhí)行的優(yōu)勢(shì)
加入我們現(xiàn)在有一個(gè)任務(wù),就是分別將a
和b
兩個(gè)變量都自增20億次,我們來(lái)看看使用兩個(gè)線程和單獨(dú)使用一個(gè)線程分別所需的時(shí)間是多少。
public class Test { private static final long COUNT = 20_0000_0000L; //兩個(gè)線程 public static void many() throws InterruptedException { //獲取開始執(zhí)行時(shí)間戳 long start = System.currentTimeMillis(); Thread thread1 = new Thread(() -> { long a = 0; for (long i = 0; i < COUNT; i++) { a++; } }); thread1.start(); Thread thread2 = new Thread(() -> { long b = 0; for (long i = 0; i < COUNT; i++) { b++; } }); thread2.start(); //等待兩個(gè)線程結(jié)束 再獲取結(jié)束時(shí)的時(shí)間戳 thread1.join(); thread2.join(); long end = System.currentTimeMillis(); //執(zhí)行時(shí)間,單位為毫秒 System.out.println("多線程執(zhí)行時(shí)間:" + (end - start) + "ms"); } //單線程 public static void single() { //記錄開始執(zhí)行的時(shí)間戳 long start = System.currentTimeMillis(); long a = 0; for (long i = 0; i < COUNT; i++) { a++; } long b = 0; for (long i = 0; i < COUNT; i++) { b++; } //獲取執(zhí)行結(jié)束時(shí)的時(shí)間戳 long end = System.currentTimeMillis(); System.out.println("單線程執(zhí)行時(shí)間:" + (end - start) + "ms"); } public static void main(String[] args) throws InterruptedException { //多線程 many(); //單線程 single(); } }
我們來(lái)看看完成這個(gè)任務(wù)所需的時(shí)間:
根據(jù)結(jié)果我們發(fā)現(xiàn)兩個(gè)線程并發(fā)執(zhí)行的時(shí)間大約是500ms
左右,單線程執(zhí)行的時(shí)間大約是1000ms
左右,當(dāng)然如果任務(wù)量不夠大,可能多線程相比于單線程并不會(huì)有優(yōu)勢(shì),畢竟創(chuàng)建線程本身還是有開銷的。
2.Thread類的常用屬性與方法
2.1Thread類中的重要屬性
屬性 | 獲取該屬性的方法 |
---|---|
線程的唯一標(biāo)識(shí)ID | public long getId() |
線程的名稱name | public final String getName() |
線程的狀態(tài)state | public State getState() |
線程的優(yōu)先級(jí)priority | public final int getPriority() |
線程是否后臺(tái)線程 | public final boolean isDaemon() |
線程是否存活 | public final native boolean isAlive() |
線程是否中斷 | public boolean isInterrupted() |
每一個(gè)線程都擁有一個(gè)id
作為標(biāo)識(shí),其中處于同一進(jìn)程的所有線程id
相同,每個(gè)進(jìn)程間都有唯一的id
標(biāo)識(shí)。
線程也是擁有名字的,如果我們創(chuàng)建Thread
對(duì)象時(shí),沒(méi)有指定線程對(duì)象的名稱,則會(huì)默認(rèn)命名為Thread-i
,其中i
為整數(shù)。
通過(guò)了解進(jìn)程,我們知道進(jìn)程擁有3
種狀態(tài),分別為阻塞,執(zhí)行和就緒。而java中的線程也有類似與這種狀態(tài)的定義,后面我們細(xì)說(shuō),優(yōu)先級(jí)也一樣就不用多說(shuō)了。
java線程分為后臺(tái)線程與前臺(tái)線程,其中后臺(tái)線程不會(huì)影響到進(jìn)程的退出,而前臺(tái)線程會(huì)影響進(jìn)程的退出,比如有線程t1
與線程t2
,當(dāng)這兩個(gè)線程為前臺(tái)線程時(shí),main
方法執(zhí)行完畢時(shí),t1
與t2
不會(huì)立即退出,要等到線程執(zhí)行完畢,整個(gè)進(jìn)程才會(huì)退出,反之,當(dāng)這兩個(gè)線程為后臺(tái)線程時(shí),main
方法執(zhí)行完畢時(shí),t1
與t2
線程被強(qiáng)制結(jié)束,整個(gè)進(jìn)程也就結(jié)束了。
關(guān)于java線程的屬性,我們可以通過(guò)java官方的jconsole
調(diào)試工具查看java線程的一些屬性。 這個(gè)工具一般在jdk的bin
目錄,
雙擊打開有如下界面:
選擇需要查看的線程并查看:
選擇你需要查看的進(jìn)程屬性:
2.2Thread類中常用方法總結(jié)
2.2.1常用方法
方法名 | 解釋 |
---|---|
public void run() | 該方法用來(lái)封裝線程運(yùn)行時(shí)執(zhí)行的內(nèi)容 |
public synchronized void start() | 線程創(chuàng)建并執(zhí)行run方法 |
public static native void sleep(long millis) throws InterruptedException | 使線程休眠millis毫秒 |
public final void join() throws InterruptedException | 等待線程結(jié)束(在哪個(gè)線程中調(diào)用哪個(gè)對(duì)象的join方法,哪個(gè)線程就等待哪個(gè)對(duì)象) |
public final synchronized void join(long millis) throws InterruptedException | 等待線程結(jié)束,最多等待millis毫秒 |
public final synchronized void join(long millis, int nanos) throws InterruptedException | 指定最多等待時(shí)間等待線程,精確到納秒 |
public void interrupt() | 中斷線程對(duì)象所關(guān)聯(lián)的對(duì)象,如果線程在休眠(阻塞狀態(tài))會(huì)拋出異常通知,否則設(shè)置中斷標(biāo)志位 |
public static boolean interrupted() | 判斷當(dāng)前線程的中斷標(biāo)志位是否設(shè)置,調(diào)用后會(huì)清除線程的中斷標(biāo)志位 |
public boolean isInterrupted() | 判斷當(dāng)前線程的中斷標(biāo)志位是否設(shè)置,調(diào)用后不會(huì)影響線程的標(biāo)志位 |
public final synchronized void setName(String name) | 修改線程對(duì)象名稱 |
public static native Thread currentThread() | 獲取當(dāng)前線程對(duì)象 |
2.2.2中斷線程
如果我們想中斷一個(gè)正在執(zhí)行的線程,該如何做呢?最簡(jiǎn)單但不嚴(yán)謹(jǐn)?shù)姆椒ň褪俏覀冊(cè)?code>run方法中定義一個(gè)中斷標(biāo)志位(需要中斷時(shí)標(biāo)志位為true
,默認(rèn)情況為false
),每次執(zhí)行具體任務(wù)時(shí)需要先判斷中斷標(biāo)志位是否為true
,如果是就結(jié)束線程,否則繼續(xù)執(zhí)行。
public class TestDemo8 { private static boolean isQuit = false; public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while(!isQuit) { //每隔1秒打印一句 System.out.println("一個(gè)不起眼的線程!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); //main線程阻塞5秒 按理會(huì)打印5句話 Thread.sleep(5000); isQuit = true; } }
運(yùn)行結(jié)果:
但是該方法是不夠嚴(yán)謹(jǐn)?shù)模行﹫?chǎng)景可能達(dá)不到預(yù)期的效果,最優(yōu)的做法就是調(diào)整線程對(duì)象或者線程類中的自帶標(biāo)志位。
方式1:使用Thread對(duì)象中的標(biāo)志位首先使用 currentThread
方法獲取線程對(duì)象,然后再調(diào)用該對(duì)象中的isterrupted
方法獲取該對(duì)象的中斷標(biāo)志位代替我們自己所寫的isQuit
標(biāo)志位,然后等該線程運(yùn)行一段時(shí)間后使用interrupt
方法改變標(biāo)志位,中斷線程,寫出如下代碼,看看能不能達(dá)到預(yù)期效果:
public class TestDemo9 { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() ->{ while (!Thread.currentThread().isInterrupted()) { System.out.println("又是一個(gè)不起眼的線程!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); //main休眠5秒 Thread.sleep(5000); //使用interrupt方法修改線程標(biāo)志位,使其中斷 thread.interrupt(); } }
我們來(lái)看一看:
失敗了,拋出一個(gè)InterruptedException
異常后,線程沒(méi)有中斷,而是繼續(xù)運(yùn)行,原因是interrupt
方法遇到因?yàn)檎{(diào)用 wait/join/sleep
等方法而阻塞的線程時(shí)會(huì)使sleep
等方法拋出異常,并且中斷標(biāo)志位不會(huì)修改為true
,這時(shí)我們的catch
語(yǔ)句里面值輸出了異常信息并沒(méi)有去中斷異常,所以我們需要在catch
語(yǔ)句中加上線程結(jié)束的收尾工作代碼和退出任務(wù)循環(huán)的break
語(yǔ)句就可以了。
public class TestDemo9 { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() ->{ while (!Thread.currentThread().isInterrupted()) { System.out.println("又是一個(gè)不起眼的線程!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); //收尾工作 System.out.println("收尾工作!"); break; } } }); thread.start(); //main休眠5秒 Thread.sleep(5000); //使用interrupt方法修改線程標(biāo)志位,使其中斷 thread.interrupt(); } }
運(yùn)行結(jié)果:
方式2:使用Thread類中的標(biāo)志位除了isInterrupted
,還有一個(gè)靜態(tài)方法interrupted
能夠訪問(wèn)類中的標(biāo)志位,一般一個(gè)程序中只有一個(gè),我們也可以使用該靜態(tài)方法來(lái)作為中斷標(biāo)志位,然后到時(shí)機(jī)后使用interrupt
方法來(lái)中斷線程執(zhí)行。
public class TestDemo10 { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while (!Thread.interrupted()) { System.out.println("又又是一個(gè)不起眼的線程!"); try { //設(shè)置打印頻率為1s Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); //收尾工作 System.out.println("收尾工作!"); break; } } }); thread.start(); //main休眠5秒 Thread.sleep(5000); //使用interrupt方法修改線程標(biāo)志位,使其中斷 thread.interrupt(); } }
運(yùn)行結(jié)果:
綜上所述,一般以方式1的方式無(wú)腦中斷線程就可以。
2.2.3線程等待
像上面的計(jì)算自增20億次的例子就需要線程等待join
方法,main
線程需要等兩個(gè)線程運(yùn)行完畢后才能計(jì)算計(jì)算結(jié)束時(shí)的時(shí)間戳。
針對(duì)這一點(diǎn)java還準(zhǔn)備了帶參數(shù)的join
方法,可以指定最長(zhǎng)的等待時(shí)間。
還有一個(gè)細(xì)節(jié)那join
方法是誰(shuí)等誰(shuí)呢?
我們來(lái)假設(shè)幾個(gè)線程,線程A表示調(diào)用join
方法的線程,線程B表示join
方法來(lái)自B線程對(duì)象,那么在A線程使用B.join
方法,那就是A線程等待B線程結(jié)束。
2.2.4start方法與run方法的區(qū)別
我們知道執(zhí)行一個(gè)線程的任務(wù)就是線程對(duì)象中所重寫的run
方法,那么可以直接調(diào)用run
方法來(lái)代替start
方法嗎?
當(dāng)然不行!因?yàn)槟阏{(diào)用run
方法就是單純地調(diào)用了Thread對(duì)象中的一個(gè)普通方法而已,并沒(méi)有創(chuàng)建一個(gè)新線程來(lái)執(zhí)行run
方法,而是通過(guò)main
線程來(lái)執(zhí)行的run
方法,而使用start
方法,會(huì)創(chuàng)建一個(gè)新線程并執(zhí)行run
方法。
3.Java線程的狀態(tài)
3.1java線程中的基本狀態(tài)
操作系統(tǒng)中進(jìn)程的狀態(tài)有三種分別為阻塞,就緒和執(zhí)行,而java線程中的狀態(tài)基本上相同,但做了細(xì)分,有一點(diǎn)區(qū)別,我們來(lái)認(rèn)識(shí)一下。
NEW
: 安排了工作, 還未開始行動(dòng),就是線程對(duì)象存在,但沒(méi)有執(zhí)行start
方法,java內(nèi)部的狀態(tài),與進(jìn)程中的狀態(tài)無(wú)關(guān)。RUNNABLE
: 就緒狀態(tài)。BLOCKED
: 線程正在等待鎖釋放而引起的阻塞狀態(tài)(synchronized加鎖)。WAITING
: 線程正在等待等待喚醒而引起的阻塞狀態(tài)(waitf方法使線程等待喚醒)。TIMED_WAITING
: 在一段時(shí)間內(nèi)處于阻塞狀態(tài),通常是使用sleep
或者join(帶參數(shù))
方法引起。TERMINATED
:Thread對(duì)象還存在,但是關(guān)聯(lián)的線程已經(jīng)工作完成了,java內(nèi)部的狀態(tài),與進(jìn)程中的狀態(tài)無(wú)關(guān)。
3.2線程狀態(tài)轉(zhuǎn)移
我先使用一個(gè)流程圖來(lái)簡(jiǎn)要說(shuō)明狀態(tài)之間的關(guān)系:
上面這個(gè)圖簡(jiǎn)單地說(shuō)明了這幾種狀態(tài)之間的轉(zhuǎn)移,關(guān)于圖中的wait
以及synchronized
關(guān)鍵字會(huì)在討論線程安全問(wèn)題時(shí)介紹。
這期的內(nèi)容分享了有關(guān)線程創(chuàng)建執(zhí)行以及有關(guān)Thread類中的基本方法,下期繼續(xù)介紹多線程更深入的知識(shí),比如線程安全問(wèn)題,如何加鎖等更深一點(diǎn)的內(nèi)容。
到此這篇關(guān)于Java線程創(chuàng)建與Thread類的使用方法的文章就介紹到這了,更多相關(guān)Java線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決異常FileNotFoundException:class path resource找不到資源文件的問(wèn)題
今天小編就為大家分享一篇關(guān)于解決異常FileNotFoundException:class path resource找不到資源文件的問(wèn)題,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12手把手教你如何用JAVA連接MYSQL(mysql-connector-j-8.0.32.jar)
這篇文章主要介紹了關(guān)于如何用JAVA連接MYSQL(mysql-connector-j-8.0.32.jar)的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用MySQL具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01idea中如何過(guò)濾某些文件不提交的方法實(shí)現(xiàn)
本文主要介紹了idea中如何過(guò)濾某些文件不提交,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07Java 動(dòng)態(tài)數(shù)組的實(shí)現(xiàn)示例
Java動(dòng)態(tài)數(shù)組是一種可以任意伸縮數(shù)組長(zhǎng)度的對(duì)象,本文主要介紹了Java 動(dòng)態(tài)數(shù)組的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Springboot通過(guò)lucene實(shí)現(xiàn)全文檢索詳解流程
Lucene是一個(gè)基于Java的全文信息檢索工具包,它不是一個(gè)完整的搜索應(yīng)用程序,而是為你的應(yīng)用程序提供索引和搜索功能。Lucene 目前是 Apache Jakarta 家族中的一個(gè)開源項(xiàng)目,也是目前最為流行的基于 Java 開源全文檢索工具包2022-06-06