Java線程創(chuàng)建與Thread類的使用方法
1.線程與Thread類
1.1操作系統(tǒng)中的線程與Java線程
1.1.1線程與Thread類
線程是操作系統(tǒng)中的概念. 操作系統(tǒng)內(nèi)核實現(xiàn)了線程這樣的機制, 并且對用戶層提供了一些 API 供用戶使用(例如 Linux 的 pthread 庫).
Java 標準庫中 Thread 類可以視為是對操作系統(tǒng)提供的 API 進行了進一步的抽象和封裝. 也就是說Thread類的一個實例就對應著一個線程。
1.1.2Thread類的構造方法
| 序號 | 方法名 | 解釋 |
|---|---|---|
| 1 | public Thread() | 無參數(shù)構造方法 |
| 2 | public Thread(Runnable target) | 傳入實現(xiàn)Runnable接口的對象(任務對象)構造線程 |
| 3 | public Thread(Runnable target, String name) | 根據(jù)目標任務并指定線程名創(chuàng)建線程 |
| 4 | public Thread(ThreadGroup group, Runnable target) | 根據(jù)線程組和任務創(chuàng)建線程(了解) |
| 5 | public Thread(ThreadGroup group, Runnable target, String name) | 比構造方法4多一個指定線程名 |
| 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) | 構造函數(shù)與構造方法5相同,只是它允許指定線程堆棧大小 |
注:線程可以被用來分組管理,分好的組即為線程組,Runnable類表示任務類,也就是線程需執(zhí)行的任務。
1.1.3啟用java線程必會的方法
想要使用java線程至少得知道Thread類中這幾個方法:
| 方法名 | 解釋 |
|---|---|
| public void run() | 該方法用來封裝線程運行時執(zhí)行的內(nèi)容 |
| public synchronized void start() | 線程創(chuàng)建并執(zhí)行run方法 |
| public static native void sleep(long millis) throws InterruptedException | 使線程休眠millis毫秒 |
創(chuàng)建Thread對象,必須重寫run方法,因為你創(chuàng)建一個線程肯定要用運行一些代碼嘛。
1.2第一個Java多線程程序
首先,我們可以創(chuàng)建一個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線程對象,但是線程沒有創(chuàng)建
Thread thread = new MyThread();
//線程創(chuàng)建并運行
thread.start();
}
}使用new創(chuàng)建線程對象,線程并沒有被創(chuàng)建,僅僅只是單純地創(chuàng)建了一個線程對象,運行start方法時才會創(chuàng)建線程并執(zhí)行run方法。
運行結果:

1.3使用Runnable對象創(chuàng)建線程
除了使用子類繼承Thread類并重寫run方法,使用子類實現(xiàn)Runnable接口(該接口中也有一個run方法,表示任務的內(nèi)容),該對象可以理解為“任務”,也就是說Thread對象可以接受Runnable引用,并執(zhí)行Runnable引用的run方法。
因為Runable是一個接口,所以需要實現(xiàn)run方法,線程Thread對象創(chuàng)建好后,此時線程并沒有創(chuàng)建運行,需要調(diào)用start方法來創(chuàng)建啟動線程。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("使用Runnable描述任務!");
}
}
public class TestDemo3 {
public static void main(String[] args) {
//將Runnable任務傳給Thread對象來創(chuàng)建運行線程
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}運行結果:

根據(jù)“低內(nèi)聚,高耦合”的
編程風格,使用Runnable的方式創(chuàng)建更優(yōu)。
1.4使用內(nèi)部類創(chuàng)建線程
當然也可以使用匿名內(nè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)建線程匿名對象");
}
};
thread.start();
}
}運行結果:

1.5使用Lambda表達式創(chuàng)建線程
使用Lambda表達式,本質(zhì)還是使用匿名內(nèi)部類創(chuàng)建的Thread。
public class TestDemo6 {
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println("使用Lambda表達式表示匿名內(nèi)部類來創(chuàng)建匿名任務"));
thread.start();
}
}運行結果:

1.6多線程并發(fā)執(zhí)行簡單演示
在一個進程中至少會有一個線程,如果不使用多線程編程,一個進程中默認會有執(zhí)行main方法的main線程(該線程是自動創(chuàng)建的),當你創(chuàng)建一個新的線程t,該線程會與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方法設定線程睡眠時間
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方法設定線程睡眠時間
try {
Thread.sleep(1000);//每執(zhí)行一次循環(huán)就睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}運行結果:

從上面的運行結果可以看出一個問題,因為thread線程與main線程都是每打印一句語句線程休眠1秒,兩個線程喚醒的先后順序是隨機的,這也是java多線程中的一個“萬惡之源”,這個問題給我們帶來了很多麻煩,細節(jié)等后續(xù)的博客細說。
1.7多線程并發(fā)執(zhí)行的優(yōu)勢
加入我們現(xiàn)在有一個任務,就是分別將a和b兩個變量都自增20億次,我們來看看使用兩個線程和單獨使用一個線程分別所需的時間是多少。
public class Test {
private static final long COUNT = 20_0000_0000L;
//兩個線程
public static void many() throws InterruptedException {
//獲取開始執(zhí)行時間戳
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();
//等待兩個線程結束 再獲取結束時的時間戳
thread1.join();
thread2.join();
long end = System.currentTimeMillis();
//執(zhí)行時間,單位為毫秒
System.out.println("多線程執(zhí)行時間:" + (end - start) + "ms");
}
//單線程
public static void single() {
//記錄開始執(zhí)行的時間戳
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í)行結束時的時間戳
long end = System.currentTimeMillis();
System.out.println("單線程執(zhí)行時間:" + (end - start) + "ms");
}
public static void main(String[] args) throws InterruptedException {
//多線程
many();
//單線程
single();
}
}我們來看看完成這個任務所需的時間:

根據(jù)結果我們發(fā)現(xiàn)兩個線程并發(fā)執(zhí)行的時間大約是500ms左右,單線程執(zhí)行的時間大約是1000ms左右,當然如果任務量不夠大,可能多線程相比于單線程并不會有優(yōu)勢,畢竟創(chuàng)建線程本身還是有開銷的。
2.Thread類的常用屬性與方法
2.1Thread類中的重要屬性
| 屬性 | 獲取該屬性的方法 |
|---|---|
線程的唯一標識ID | public long getId() |
線程的名稱name | public final String getName() |
線程的狀態(tài)state | public State getState() |
線程的優(yōu)先級priority | public final int getPriority() |
| 線程是否后臺線程 | public final boolean isDaemon() |
| 線程是否存活 | public final native boolean isAlive() |
| 線程是否中斷 | public boolean isInterrupted() |
每一個線程都擁有一個id作為標識,其中處于同一進程的所有線程id相同,每個進程間都有唯一的id標識。
線程也是擁有名字的,如果我們創(chuàng)建Thread對象時,沒有指定線程對象的名稱,則會默認命名為Thread-i,其中i為整數(shù)。
通過了解進程,我們知道進程擁有3種狀態(tài),分別為阻塞,執(zhí)行和就緒。而java中的線程也有類似與這種狀態(tài)的定義,后面我們細說,優(yōu)先級也一樣就不用多說了。
java線程分為后臺線程與前臺線程,其中后臺線程不會影響到進程的退出,而前臺線程會影響進程的退出,比如有線程t1與線程t2,當這兩個線程為前臺線程時,main方法執(zhí)行完畢時,t1與t2不會立即退出,要等到線程執(zhí)行完畢,整個進程才會退出,反之,當這兩個線程為后臺線程時,main方法執(zhí)行完畢時,t1與t2線程被強制結束,整個進程也就結束了。
關于java線程的屬性,我們可以通過java官方的jconsole調(diào)試工具查看java線程的一些屬性。 這個工具一般在jdk的bin目錄,

雙擊打開有如下界面:

選擇需要查看的線程并查看:

選擇你需要查看的進程屬性:

2.2Thread類中常用方法總結
2.2.1常用方法
| 方法名 | 解釋 |
|---|---|
| public void run() | 該方法用來封裝線程運行時執(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 | 等待線程結束(在哪個線程中調(diào)用哪個對象的join方法,哪個線程就等待哪個對象) |
| public final synchronized void join(long millis) throws InterruptedException | 等待線程結束,最多等待millis毫秒 |
| public final synchronized void join(long millis, int nanos) throws InterruptedException | 指定最多等待時間等待線程,精確到納秒 |
| public void interrupt() | 中斷線程對象所關聯(lián)的對象,如果線程在休眠(阻塞狀態(tài))會拋出異常通知,否則設置中斷標志位 |
| public static boolean interrupted() | 判斷當前線程的中斷標志位是否設置,調(diào)用后會清除線程的中斷標志位 |
| public boolean isInterrupted() | 判斷當前線程的中斷標志位是否設置,調(diào)用后不會影響線程的標志位 |
| public final synchronized void setName(String name) | 修改線程對象名稱 |
| public static native Thread currentThread() | 獲取當前線程對象 |
2.2.2中斷線程
如果我們想中斷一個正在執(zhí)行的線程,該如何做呢?最簡單但不嚴謹?shù)姆椒ň褪俏覀冊?code>run方法中定義一個中斷標志位(需要中斷時標志位為true,默認情況為false),每次執(zhí)行具體任務時需要先判斷中斷標志位是否為true,如果是就結束線程,否則繼續(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("一個不起眼的線程!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
//main線程阻塞5秒 按理會打印5句話
Thread.sleep(5000);
isQuit = true;
}
}運行結果:

但是該方法是不夠嚴謹?shù)模行﹫鼍翱赡苓_不到預期的效果,最優(yōu)的做法就是調(diào)整線程對象或者線程類中的自帶標志位。
方式1:使用Thread對象中的標志位首先使用 currentThread方法獲取線程對象,然后再調(diào)用該對象中的isterrupted方法獲取該對象的中斷標志位代替我們自己所寫的isQuit標志位,然后等該線程運行一段時間后使用interrupt方法改變標志位,中斷線程,寫出如下代碼,看看能不能達到預期效果:
public class TestDemo9 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
while (!Thread.currentThread().isInterrupted()) {
System.out.println("又是一個不起眼的線程!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
//main休眠5秒
Thread.sleep(5000);
//使用interrupt方法修改線程標志位,使其中斷
thread.interrupt();
}
}我們來看一看:

失敗了,拋出一個InterruptedException異常后,線程沒有中斷,而是繼續(xù)運行,原因是interrupt方法遇到因為調(diào)用 wait/join/sleep 等方法而阻塞的線程時會使sleep等方法拋出異常,并且中斷標志位不會修改為true,這時我們的catch語句里面值輸出了異常信息并沒有去中斷異常,所以我們需要在catch語句中加上線程結束的收尾工作代碼和退出任務循環(huán)的break語句就可以了。
public class TestDemo9 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
while (!Thread.currentThread().isInterrupted()) {
System.out.println("又是一個不起眼的線程!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//收尾工作
System.out.println("收尾工作!");
break;
}
}
});
thread.start();
//main休眠5秒
Thread.sleep(5000);
//使用interrupt方法修改線程標志位,使其中斷
thread.interrupt();
}
}運行結果:

方式2:使用Thread類中的標志位除了isInterrupted,還有一個靜態(tài)方法interrupted能夠訪問類中的標志位,一般一個程序中只有一個,我們也可以使用該靜態(tài)方法來作為中斷標志位,然后到時機后使用interrupt方法來中斷線程執(zhí)行。
public class TestDemo10 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (!Thread.interrupted()) {
System.out.println("又又是一個不起眼的線程!");
try {
//設置打印頻率為1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//收尾工作
System.out.println("收尾工作!");
break;
}
}
});
thread.start();
//main休眠5秒
Thread.sleep(5000);
//使用interrupt方法修改線程標志位,使其中斷
thread.interrupt();
}
}運行結果:

綜上所述,一般以方式1的方式無腦中斷線程就可以。
2.2.3線程等待
像上面的計算自增20億次的例子就需要線程等待join方法,main線程需要等兩個線程運行完畢后才能計算計算結束時的時間戳。
針對這一點java還準備了帶參數(shù)的join方法,可以指定最長的等待時間。
還有一個細節(jié)那join方法是誰等誰呢?
我們來假設幾個線程,線程A表示調(diào)用join方法的線程,線程B表示join方法來自B線程對象,那么在A線程使用B.join方法,那就是A線程等待B線程結束。
2.2.4start方法與run方法的區(qū)別
我們知道執(zhí)行一個線程的任務就是線程對象中所重寫的run方法,那么可以直接調(diào)用run方法來代替start方法嗎?
當然不行!因為你調(diào)用run方法就是單純地調(diào)用了Thread對象中的一個普通方法而已,并沒有創(chuàng)建一個新線程來執(zhí)行run方法,而是通過main線程來執(zhí)行的run方法,而使用start方法,會創(chuàng)建一個新線程并執(zhí)行run方法。
3.Java線程的狀態(tài)
3.1java線程中的基本狀態(tài)
操作系統(tǒng)中進程的狀態(tài)有三種分別為阻塞,就緒和執(zhí)行,而java線程中的狀態(tài)基本上相同,但做了細分,有一點區(qū)別,我們來認識一下。
NEW: 安排了工作, 還未開始行動,就是線程對象存在,但沒有執(zhí)行start方法,java內(nèi)部的狀態(tài),與進程中的狀態(tài)無關。RUNNABLE: 就緒狀態(tài)。BLOCKED: 線程正在等待鎖釋放而引起的阻塞狀態(tài)(synchronized加鎖)。WAITING: 線程正在等待等待喚醒而引起的阻塞狀態(tài)(waitf方法使線程等待喚醒)。TIMED_WAITING: 在一段時間內(nèi)處于阻塞狀態(tài),通常是使用sleep或者join(帶參數(shù))方法引起。TERMINATED:Thread對象還存在,但是關聯(lián)的線程已經(jīng)工作完成了,java內(nèi)部的狀態(tài),與進程中的狀態(tài)無關。
3.2線程狀態(tài)轉移
我先使用一個流程圖來簡要說明狀態(tài)之間的關系:

上面這個圖簡單地說明了這幾種狀態(tài)之間的轉移,關于圖中的wait以及synchronized關鍵字會在討論線程安全問題時介紹。
這期的內(nèi)容分享了有關線程創(chuàng)建執(zhí)行以及有關Thread類中的基本方法,下期繼續(xù)介紹多線程更深入的知識,比如線程安全問題,如何加鎖等更深一點的內(nèi)容。
到此這篇關于Java線程創(chuàng)建與Thread類的使用方法的文章就介紹到這了,更多相關Java線程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決異常FileNotFoundException:class path resource找不到資源文件的問題
今天小編就為大家分享一篇關于解決異常FileNotFoundException:class path resource找不到資源文件的問題,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12
手把手教你如何用JAVA連接MYSQL(mysql-connector-j-8.0.32.jar)
這篇文章主要介紹了關于如何用JAVA連接MYSQL(mysql-connector-j-8.0.32.jar)的相關資料,文中通過圖文介紹的非常詳細,對大家學習或者使用MySQL具有一定的參考借鑒價值,需要的朋友可以參考下2024-01-01
Java 動態(tài)數(shù)組的實現(xiàn)示例
Java動態(tài)數(shù)組是一種可以任意伸縮數(shù)組長度的對象,本文主要介紹了Java 動態(tài)數(shù)組的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08
Springboot通過lucene實現(xiàn)全文檢索詳解流程
Lucene是一個基于Java的全文信息檢索工具包,它不是一個完整的搜索應用程序,而是為你的應用程序提供索引和搜索功能。Lucene 目前是 Apache Jakarta 家族中的一個開源項目,也是目前最為流行的基于 Java 開源全文檢索工具包2022-06-06

