小游戲大全網(wǎng)站論壇企業(yè)推廣
目錄
- Thread構(gòu)造方法
- Thread 的常見屬性
- 創(chuàng)建一個線程
- 獲取當(dāng)前線程引用
- 終止一個線程
- 使用標(biāo)志位
- 使用自帶的標(biāo)志位
- 等待一個線程
- 線程休眠
- 線程狀態(tài)
- 線程安全
- 線程不安全原因總結(jié)
- 解決由先前線程不安全問題例子

Thread構(gòu)造方法
方法 | 說明 |
---|---|
Thread() | 創(chuàng)建線程對象 |
Thread(Runnable target) | 使用 Runnable 對象創(chuàng)建線程對象 |
Thread(String name) | 創(chuàng)建線程對象,并命名(當(dāng)前線程名) |
Thread(Runnable target, String name) | 使用 Runnable 對象創(chuàng)建線程對象,并命名 |
Thread(ThreadGroup group,Runnable target) | 線程可以被用來分組管理,分好的組即為線程組 |
Thread 的常見屬性
屬性 | 獲取方法 |
ID | getId() |
名稱 | getName() |
狀態(tài) | getState() |
優(yōu)先級 | getPriority() |
是否后臺線程 | isDaemon() |
是否存活 | isAlive() |
是否被中斷 | isInterrupted() |
解釋:
- ID 是線程的唯一標(biāo)識,不同線程不會重復(fù),但是這里的id是Java給的id,不是前面PCB中說的id。
- 名稱在各種調(diào)試工具用到,前面構(gòu)造方法給的名稱就是這個。
- 狀態(tài)表示線程當(dāng)前所處的一個情況。
- 優(yōu)先級高的線程理論上來說更容易被調(diào)度到,但是這個是系統(tǒng)微觀程度上的,很難感知到。
- 關(guān)于后臺線程,需要記住一點:JVM會在一個進(jìn)程的所有非后臺線程(前臺線程)結(jié)束后,才會結(jié)束運行,而后臺線程不影響Java進(jìn)程的結(jié)束,可以在start()調(diào)用前使用setDaemon(true)來設(shè)置線程為后臺線程。
- 是否存活,即簡單的理解,為 run 方法是否運行結(jié)束了
創(chuàng)建一個線程
在前一篇文章中就介紹了相關(guān)操作,在這簡單提一下一定要使用線程變量名.start();
創(chuàng)建一個新線程,start()方法是Java提供的API來調(diào)用系統(tǒng)中創(chuàng)建線程的方法。而run()方法是這個線程要干的事情,在線程創(chuàng)建好之后自動就會調(diào)用。
每個線程對象只能start一次。
獲取當(dāng)前線程引用
方法 | 說明 |
---|---|
public static Thread currentThread(); | 返回當(dāng)前線程對象的引用 |
是靜態(tài)方法直接使用Thread.currentThread();
就可以獲取到當(dāng)前的線程引用。
終止一個線程
在Java中終止一個線程的思路就是讓線程中的run()方法盡快結(jié)束。
使用標(biāo)志位
由于線程遲遲不結(jié)束大多是因為里面有循環(huán)語句,我們就可以使用一個成員變量來控制循環(huán)的結(jié)束。
不能使用局部變量定義在main方法內(nèi),因為雖然lambda表達(dá)式可以捕獲上層變量,但是這個變量不可以進(jìn)行修改。
public class Demo {private static boolean isQuit = false;public static void main(String[] args) {Thread thread = new Thread(() ->{while(isQuit) {//具體操作 }});thread.start();isQuit = true;}
}
使用自帶的標(biāo)志位
方法 | 說明 |
---|---|
public void interrupt() | 中斷對象關(guān)聯(lián)的線程,如果線程正在阻塞,則以異常方式通知,否則設(shè)置標(biāo)志位 |
public static boolean interrupted() | 判斷當(dāng)前線程的中斷標(biāo)志位是否設(shè)置,調(diào)用后清除標(biāo)志位,不建議使用,靜態(tài)方法為所有線程共用的 |
public boolean isInterrupted() | 判斷對象關(guān)聯(lián)的線程的標(biāo)志位是否設(shè)置,調(diào)用后不清除標(biāo)志位 |
Java中自帶了標(biāo)志位來標(biāo)志是否結(jié)束循環(huán)。先使用Thread.currentThread()
獲取到當(dāng)前線程,在.isInterrupted()
獲取標(biāo)志位。然后再主進(jìn)程中調(diào)用interrupte()
方法來將標(biāo)志位值修改為true。
public class Demo {public static void main(String[] args) {Thread thread = new Thread(() ->{while (!Thread.currentThread().isInterrupted()) {//操作}});thread.start();thread.interrupt();}
}
但是如果在線程中有捕獲InterruptedException
異常的語句,那么會在調(diào)用interrupte()
同時捕獲到該異常,并且消除標(biāo)志位。
此時我們就可以在catch語句中自己選擇是將線程結(jié)束還是進(jìn)行其它操作。
public class Demo {public static void main(String[] args) {Thread thread = new Thread(() ->{while (!Thread.currentThread().isInterrupted()) {try {Thread.sleep(1000);} catch (InterruptedException e) {//1.不操作繼續(xù)執(zhí)行線程e.printStackTrace();//2.結(jié)束線程break;//3.進(jìn)行其它操作}}});thread.start();thread.interrupt();}
}
等待一個線程
方法 | 說明 |
---|---|
public void join() | 等待線程結(jié)束 |
public void join(long millis) | 等待線程結(jié)束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 等待線程結(jié)束,最多等 millis 毫秒,但可以更高精度 |
在主線程中調(diào)用線程對象.join();
就是等待線程對象執(zhí)行完再執(zhí)行主線程。
調(diào)用細(xì)節(jié):
- 調(diào)用
線程對象.join();
就會讓該線程執(zhí)行完才繼續(xù)執(zhí)行外面的線程,如果線程對象對應(yīng)的線程一直不結(jié)束那么外面的線程就會一直等(死等) - 調(diào)用
線程對象.join(long millis);
就會在該線程執(zhí)行millis毫秒后執(zhí)行外面的線程。 - 如果遇到調(diào)用join前線程已經(jīng)結(jié)束,外面的線程不會陷入等待。
如下代碼執(zhí)行結(jié)果就是先打印5個thread線程,最后在打印main線程:
public class Demo6 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {for(int i = 0; i < 5; i++) {System.out.println("thread線程");}}); thread。start();thread.join();System.out.println("main線程");}
}
線程休眠
方法 | 說明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠當(dāng)前線程 millis毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
在系統(tǒng)讓線程休眠sleep中的參數(shù)毫秒后,線程會被喚醒從阻塞狀態(tài)變成就緒狀態(tài),但不會馬上執(zhí)行,涉及到調(diào)度開銷。所以實際使用的時間是大于sleep中的參數(shù)的。
并且在Windows和Linux系統(tǒng)上達(dá)到毫秒級誤差。
線程狀態(tài)
在操作系統(tǒng)里面進(jìn)程和線程最重要的狀態(tài)就是:就緒狀態(tài)和阻塞狀態(tài)。
在Java中又給線程又給線程賦予了一些其他狀態(tài)。
線程的狀態(tài)是一個枚舉類型 Thread.State。
狀態(tài) | 說明 |
---|---|
new | Thread對象已經(jīng)創(chuàng)建,但是start方法沒有調(diào)用 |
terminated | Thread對象還在,但是內(nèi)核中線程已將結(jié)束了 |
Runnable | 就緒狀態(tài),線程已經(jīng)在CPU上執(zhí)行或者在CPU上等待執(zhí)行 |
timed_waiting | 由于sleep這種固定時間產(chǎn)生的阻塞 |
waiting | 由于wait這種不固定時間產(chǎn)生的阻塞 |
blocked | 由于鎖競爭產(chǎn)生的阻塞 |
線程安全
線程安全的簡單說法就是符不符合預(yù)期:如果多線程環(huán)境下代碼運行的結(jié)果是符合我們預(yù)期的,即在單線程環(huán)境應(yīng)該的結(jié)果,則說這個程序是線程安全的。
例如以下代碼:
我們的預(yù)期結(jié)果是10000,但是其實每次的結(jié)果都是不一樣的,這種就是線程不安全。
public class Demo {private static int ret;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {ret++;}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {ret++;}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(ret);;}
}
就以上訴代碼例子來講解出現(xiàn)線程不安全的原因。
在CPU上實現(xiàn)自增操作主要有三步:
- 將數(shù)據(jù)給到CPU的寄存器中;
- 數(shù)據(jù)在寄存器中加1;
- 將數(shù)據(jù)返回到內(nèi)存中。
就以一個thread1和一個thread2來說,每個線程都進(jìn)行這三步操作,但是線程在CPU上又是隨機調(diào)用的,這就相當(dāng)于有六個位置隨機坐,相當(dāng)于排列組合的A66,當(dāng)數(shù)據(jù)作為不同線程的開始值進(jìn)入寄存器時就相當(dāng)于兩次自增只執(zhí)行了一次。
但是線程調(diào)用就更加復(fù)雜了,線程數(shù)量不一樣,順序不一樣,這就相當(dāng)于有無數(shù)種可能了,所以結(jié)果是不可控的,就導(dǎo)致了線程不安全的情況。
線程不安全原因總結(jié)
在介紹線程不安全原因之前先介紹一個概念:原子性。
原子性:簡單來講就是執(zhí)行一段代碼連續(xù)執(zhí)行完不被其他線程干擾。舉個例子:
我們把一段代碼想象成一個房間,每個線程就是要進(jìn)入這個房間的人。如果沒有任何機制保證,A進(jìn)入房間之后,還沒有出來;B 是不是也可以進(jìn)入房間,打斷 A 在房間里的隱私。這個就是不具備原子性的。
那我們應(yīng)該如何解決這個問題呢?是不是只要給房間加一把鎖,A 進(jìn)去就把門鎖上,其他人是不是就進(jìn)不來了。這樣就保證了這段代碼的原子性了。
有時也把這個現(xiàn)象叫做同步互斥,表示操作是互相排斥的。
原因總結(jié):
- 操作系統(tǒng)調(diào)度線程是隨機的(搶占式執(zhí)行);
- 多個線程對同一個變量進(jìn)行修改;
- 修改操作不是原子性的;
- 內(nèi)存可見性問題;
- 指令重排序問題。
解決由先前線程不安全問題例子
要解決就要從原因入手:
- 操作系統(tǒng)隨機調(diào)度是操作系統(tǒng)帶來的解決不了;
- 多個線程對一個變量修改,有些可以規(guī)避,但有些根據(jù)需求無法規(guī)避。
- 將操作改為原子性,可以通過synchronized關(guān)鍵字 加鎖操作來實現(xiàn)。
語法:
synchronized(變量){
//修改操作
}
()括號內(nèi)的變量不重要,作用是區(qū)分加鎖對象是否一樣,如果對同一個對象加鎖,那么兩個操作就會產(chǎn)生“blocked”鎖競爭阻塞問題,后一個線程就會等到前一個線程解鎖再執(zhí)行。
進(jìn)入左大括號 ‘{’ 就是加鎖,出了右大括號 ‘}’ 就是解鎖。
對上訴代碼進(jìn)行如下修改,就會出現(xiàn)預(yù)期結(jié)果10000:
public class Demo7 {private static int ret;public static void main(String[] args) throws InterruptedException {Object block = new Object();Thread thread1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (block){ret++;}}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (block){ret++;}}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(ret);;}
}
synchronized還可以修飾方法(靜態(tài)方法也行)。
- synchronized修飾實例方法:
class Counter{public int ret;public void increase1() {synchronized (this) {ret++;}}//簡化版本synchronized public void increase2() {ret++;}
}
- synchronized修飾靜態(tài)方法:相當(dāng)于修飾這個類
class Counter{private static int ret2;public static void increase3() {synchronized (Counter.class) {ret2++;}}//簡化版本synchronized public static void increase4() {ret2++;}
}