引言
在JDK17(或以上版本)中,Thread
類提供了一組常用的API,用于管理線程的創(chuàng)建、啟動(dòng)、暫停、恢復(fù)和銷毀等操作。本文從api、源碼、編程示例等方面詳細(xì)說(shuō)明Thread常用函數(shù)的使用和注意事項(xiàng)。
線程 sleep
- 使當(dāng)前正在執(zhí)行的線程暫停(掛起)指定的毫秒數(shù)。但受系統(tǒng)計(jì)時(shí)器和調(diào)度程序的精度和準(zhǔn)確性限制。
- 線程不會(huì)失去任何monitor(監(jiān)視器)的所有權(quán)。
- 每個(gè)線程的休眠互不影響,
Thread.sleep
只會(huì)導(dǎo)致當(dāng)前線程進(jìn)入指定時(shí)間的休眠。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0 && millis < Long.MAX_VALUE) {
millis++;
}
sleep(millis);
}
通過(guò)測(cè)試發(fā)現(xiàn) Thread.sleep
之間互不影響。代碼如下:
/**
* 每個(gè)線程的休眠互不影響,`Thread.sleep` 只會(huì)導(dǎo)致當(dāng)前線程進(jìn)入指定時(shí)間的休眠。
*/
public class ThreadSleepTest {
public static void main(String[] args) throws Exception{
Thread thread1 = new Thread(()->{
int i = 0;
while(i<10){
System.out.println("thread demo start "+i);
i++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread1.start();
System.out.println("thread main start ");
Thread.sleep(2000);
System.out.println("thread main end ");
}
}
輸出結(jié)果如下:
thread main start
thread demo start 0
thread demo start 1
thread main end
thread demo start 2
thread demo start 3
除此之外可以使用 java.util.concurrent.TimeUnit
類來(lái)更簡(jiǎn)單的實(shí)現(xiàn)指定時(shí)間的休眠,后續(xù)源碼使用該類來(lái)進(jìn)行休眠線程。例子代碼如下:
package engineer.concurrent.battle.abasic;
import java.util.concurrent.TimeUnit;
/**
* TimeUnit工具類替代Thread.sleep方法。
* @author r0ad
* @since 1.0
*/
public class ThreadSleepTimeUnitTest {
public static void main(String[] args) throws Exception{
System.out.println("thread main start ");
TimeUnit.SECONDS.sleep(1);
TimeUnit.MILLISECONDS.sleep(500);
TimeUnit.MINUTES.sleep(1);
System.out.println("thread main end ");
}
}
/**
* java.util.concurrent.TimeUnit#sleep 源碼,底層實(shí)現(xiàn)也是Thread.sleep。
* Performs a {@link Thread#sleep(long, int) Thread.sleep} using
* this time unit.
* This is a convenience method that converts time arguments into the
* form required by the {@code Thread.sleep} method.
*
* @param timeout the minimum time to sleep. If less than
* or equal to zero, do not sleep at all.
* @throws InterruptedException if interrupted while sleeping
*/
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
線程 yield
Thread.yield()
是一個(gè)提示,用于告訴調(diào)度程序當(dāng)前線程愿意放棄使用處理器。調(diào)度程序可以選擇忽略這個(gè)提示。Yield是一種試圖改善線程之間相對(duì)進(jìn)程的啟發(fā)式方法,否則它們會(huì)過(guò)度利用CPU。它的使用應(yīng)該與詳細(xì)的分析和基準(zhǔn)測(cè)試結(jié)合起來(lái),以確保它確實(shí)產(chǎn)生了預(yù)期的效果。
這種方法適用場(chǎng)景很少。它有助于調(diào)試或測(cè)試,以幫助重現(xiàn)由于競(jìng)態(tài)條件而引起的錯(cuò)誤。在設(shè)計(jì)并發(fā)控制結(jié)構(gòu)時(shí),例如java.util.concurrent.locks
包中的結(jié)構(gòu),它也可能有用。
調(diào)用Thread.yield()
函數(shù)會(huì)將當(dāng)前線程從RUNNING狀態(tài)切換到RUNNABLE狀態(tài)。
public static native void yield();
測(cè)試代碼如下,在cpu資源不緊張的情況下輸出仍然是亂序的。
package engineer.concurrent.battle.abasic;
import java.util.stream.IntStream;
/**
* ThreadYield測(cè)試
* @author r0ad
* @since 1.0
*/
public class ThreadYieldTest {
public static void main(String[] args) throws Exception{
System.out.println("thread main start ");
IntStream.range(0, 2).mapToObj(ThreadYieldTest::create).forEach(Thread::start);
System.out.println("thread main end ");
}
private static Thread create(int i) {
return new Thread(() -> {
if(i == 0 ){
Thread.yield();
}
System.out.println("thread " + i + " start ");
});
}
}
輸出結(jié)果:
thread main start
thread main end
thread 0 start
thread 1 start
Thread.yield()
和 Thread.sleep()
方法之間的聯(lián)系和差異如下:
聯(lián)系:
- Thread.yield() 和 Thread.sleep() 都會(huì)使當(dāng)前線程暫停執(zhí)行,讓出CPU資源給其他線程。
- Thread.yield() 和 Thread.sleep() 都不會(huì)釋放當(dāng)前線程所占用的鎖。
差異:
- Thread.yield() 方法只是暫停當(dāng)前線程的執(zhí)行,讓出CPU資源給其他線程,但不會(huì)進(jìn)入阻塞狀態(tài)??赡軐?dǎo)致CPU進(jìn)行上下文切換。
- Thread.sleep() 方法會(huì)使當(dāng)前線程暫停指定的時(shí)間,并進(jìn)入阻塞狀態(tài),直到休眠時(shí)間結(jié)束或者被其他線程打斷。
- Thread.sleep()具有較高的可靠性,可以確保至少暫停指定的時(shí)間。Thread.yield()則不能保證暫停。
設(shè)置線程的優(yōu)先級(jí)
-
java.lang.Thread#setPriority
修改線程的優(yōu)先級(jí) -
java.lang.Thread#getPriority
獲取線程的優(yōu)先級(jí)
java.lang.Thread#setPriority
修改線程的優(yōu)先級(jí)實(shí)現(xiàn)過(guò)程如下:
- 調(diào)用此線程的
checkAccess
方法,不帶任何參數(shù)。這可能會(huì)導(dǎo)致拋出一個(gè)SecurityException
異常。 - 線程的優(yōu)先級(jí)被設(shè)置為指定的
newPriority
和線程所屬線程組允許的最大優(yōu)先級(jí)中較小的值。
/**
* `java.lang.Thread#setPriority` 修改線程的優(yōu)先級(jí)源碼
*/
public final void setPriority(int newPriority) {
ThreadGroup g;
// 調(diào)用此線程的`checkAccess`方法,不帶任何參數(shù)。這可能會(huì)導(dǎo)致拋出一個(gè)`SecurityException`異常。
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
// 線程的優(yōu)先級(jí)被設(shè)置為指定的`newPriority`和線程所屬線程組允許的最大優(yōu)先級(jí)中較小的值。
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
/**
* `java.lang.Thread#getPriority` 獲取線程的優(yōu)先級(jí)
*/
public final int getPriority() {
// 返回Thread的priority屬性
return priority;
}
/* 原生優(yōu)先級(jí)設(shè)置方法 */
private native void setPriority0(int newPriority);
進(jìn)程有進(jìn)程的優(yōu)先級(jí),線程同樣也有優(yōu)先級(jí),理論上是優(yōu)先級(jí)比較高的線程會(huì)獲取優(yōu)先被 CPU 調(diào)度的機(jī)會(huì),但是事實(shí)上設(shè)置線程的優(yōu)先級(jí)同樣也是一個(gè) hint 操作,具體如下。
- 對(duì)于 root 用戶,它會(huì) hint 操作系統(tǒng)你想要設(shè)置的優(yōu)先級(jí)別,否則它會(huì)被忽略。
- 如果 CPU 比較忙,設(shè)置優(yōu)先級(jí)可能會(huì)獲得更多的 CPU 時(shí)間片,但是閑時(shí)優(yōu)先級(jí)的高低幾乎不會(huì)有任何作用。
所以不要使用線程的優(yōu)先級(jí)進(jìn)行某些特定業(yè)務(wù)的綁定,業(yè)務(wù)執(zhí)行的順序應(yīng)該還是要使用同步執(zhí)行方法來(lái)保證。
測(cè)試?yán)尤缦?,線程之間會(huì)交替輸出:
package engineer.concurrent.battle.abasic;
/**
* ThreadPriorityTest測(cè)試
* @author r0ad
* @since 1.0
*/
public class ThreadPriorityTest {
public static void main(String[] args) throws Exception{
Thread t1 = ThreadPriorityTest.create("t1");
t1.setPriority(1);
Thread t2 = ThreadPriorityTest.create("t2");
t2.setPriority(10);
t1.start();
t2.start();
}
private static Thread create(String name) {
return new Thread(() -> {
while (true) {
System.out.println("thread " + name );
}
});
}
}
獲取線程ID
返回此線程的標(biāo)識(shí)符。線程ID是一個(gè)正的long
數(shù)字,在創(chuàng)建此線程時(shí)生成。線程ID是唯一的,并在其生命周期內(nèi)保持不變。當(dāng)一個(gè)線程終止時(shí),該線程ID可能會(huì)被重新使用。
public long getId() {
return tid;
}
獲取當(dāng)前線程
java.lang.Thread#currentThread
方法被大多數(shù)框架使用,像是SpringMVC、MyBatis這些。調(diào)用該函數(shù)會(huì)返回當(dāng)前正在執(zhí)行的線程對(duì)象。
@IntrinsicCandidate
public static native Thread currentThread();
測(cè)試代碼如下:
package engineer.concurrent.battle.abasic;
/**
* ThreadCurrentTest測(cè)試
* @author r0ad
* @since 1.0
*/
public class ThreadCurrentTest {
public static void main(String[] args) throws Exception{
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println(this == Thread.currentThread());
}
};
t1.start();
System.out.println(Thread.currentThread().getName());
}
}
設(shè)置線程上下文類加載器
-
java.lang.Thread#getContextClassLoader
返回該線程的上下文ClassLoader。上下文ClassLoader由創(chuàng)建線程的對(duì)象提供,用于在此線程中運(yùn)行的代碼在加載類和資源時(shí)使用。如果未設(shè)置(通過(guò)setContextClassLoader()
方法),則默認(rèn)為父線程的ClassLoader上下文。原始線程的上下文ClassLoader通常設(shè)置為用于加載應(yīng)用程序的類加載器。 -
java.lang.Thread#setContextClassLoader
設(shè)置此線程的上下文ClassLoader。上下文ClassLoader可以在創(chuàng)建線程時(shí)設(shè)置,并允許線程的創(chuàng)建者通過(guò)getContextClassLoader
方法為在線程中運(yùn)行的代碼提供適當(dāng)?shù)念惣虞d器,用于加載類和資源。如果存在安全管理器,則會(huì)使用其checkPermission
方法,傳入RuntimePermission
的setContextClassLoader
權(quán)限,以檢查是否允許設(shè)置上下文ClassLoader。
@CallerSensitive
public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
}
public void setContextClassLoader(ClassLoader cl) {
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
contextClassLoader = cl;
}
線程 interrupt
java.lang.Thread#interrupt
中斷此線程。除非當(dāng)前線程自己中斷自己,這是始終允許的,否則會(huì)調(diào)用該線程的checkAccess
方法,可能會(huì)引發(fā)SecurityException
異常。
如果此線程在Object
類的wait()
、wait(long)
、wait(long, int)
方法的調(diào)用中被阻塞,或者在此類的join()
、join(long)
、join(long, int)
、sleep(long)
或sleep(long, int)
方法的調(diào)用中被阻塞,則它的中斷狀態(tài)將被清除,并且它將收到一個(gè)InterruptedException
異常。
如果此線程在對(duì)InterruptibleChannel
的I/O操作中被阻塞,則通道將被關(guān)閉,線程的中斷狀態(tài)將被設(shè)置,并且線程將收到一個(gè)ClosedByInterruptException
異常。
如果此線程在Selector
中被阻塞,則線程的中斷狀態(tài)將被設(shè)置,并且它將立即從選擇操作中返回,可能帶有非零值,就像調(diào)用了選擇器的wakeup
方法一樣。
如果以上條件都不滿足,則將設(shè)置此線程的中斷狀態(tài)。
中斷一個(gè)未啟動(dòng)的線程可能不會(huì)產(chǎn)生任何效果。在JDK參考實(shí)現(xiàn)中,中斷一個(gè)未啟動(dòng)的線程仍然記錄了中斷請(qǐng)求的發(fā)出,并通過(guò)interrupted
和isInterrupted()
方法報(bào)告它。
java.lang.Thread#interrupted
測(cè)試當(dāng)前線程是否已被中斷。此方法將清除線程的"中斷狀態(tài)"。換句話說(shuō),如果連續(xù)兩次調(diào)用此方法,第二次調(diào)用將返回false(除非在第一次調(diào)用清除了線程的中斷狀態(tài)之后,而第二次調(diào)用在檢查之前再次中斷了當(dāng)前線程)。
java.lang.Thread#isInterrupted
測(cè)試此線程是否已被中斷。此方法不會(huì)影響線程的"中斷狀態(tài)"。
public void interrupt() {
if (this != Thread.currentThread()) {
checkAccess();
// thread may be blocked in an I/O operation
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupted = true;
interrupt0(); // inform VM of interrupt
b.interrupt(this);
return;
}
}
}
interrupted = true;
// inform VM of interrupt
interrupt0();
}
public static boolean interrupted() {
Thread t = currentThread();
boolean interrupted = t.interrupted;
// We may have been interrupted the moment after we read the field,
// so only clear the field if we saw that it was set and will return
// true; otherwise we could lose an interrupt.
if (interrupted) {
t.interrupted = false;
clearInterruptEvent();
}
return interrupted;
}
public boolean isInterrupted() {
return interrupted;
}
測(cè)試代碼如下:
package engineer.concurrent.battle.abasic;
import java.util.concurrent.TimeUnit;
/**
* ThreadInterruptTest
* @author r0ad
* @since 1.0
*/
public class ThreadInterruptTest {
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
int i = 0;
while(i<10){
i++;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t1.start();
// 中斷該線程
t1.interrupt();
System.out.println("t1 interrupt status " + t1.isInterrupted());
System.out.println("t1 is interrupted and I can work still. ");
// 修改中斷狀態(tài),但是線程不會(huì)繼續(xù)執(zhí)行
t1.isInterrupted();
System.out.println("t1 interrupt status " + t1.isInterrupted());
}
}
線程 join
Thread 的 join 方法同樣是一個(gè)非常重要的方法,與 sleep 一樣它也是一個(gè)可中斷的方法。Thread類通過(guò)重載實(shí)現(xiàn)了三個(gè)函數(shù)供多線程開發(fā)使用。
java.lang.Thread#join(long)
等待最多millis
毫秒,讓此線程死亡。0
的超時(shí)時(shí)間意味著永久等待。此實(shí)現(xiàn)使用了一個(gè)基于this.isAlive
條件的this.wait
調(diào)用循環(huán)。當(dāng)線程終止時(shí),將調(diào)用this.notifyAll方法。建議應(yīng)用程序不要在Thread實(shí)例上使用wait、notify或notifyAll。
java.lang.Thread#join(long, int)
等待最多 millis 毫秒加上 nanos 納秒以使此線程死亡。如果兩個(gè)參數(shù)都是 0,那么意味著永遠(yuǎn)等待。此實(shí)現(xiàn)使用一個(gè)循環(huán)的 this.wait 調(diào)用,條件為 this.isAlive。當(dāng)一個(gè)線程終止時(shí),會(huì)調(diào)用 this.notifyAll 方法。建議應(yīng)用程序不要在 Thread 實(shí)例上使用 wait、notify 或 notifyAll。
java.lang.Thread#join()
等待此線程終止。調(diào)用此方法的行為與調(diào)用 join(0) 完全相同。
源碼實(shí)現(xiàn)如下:
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
throw new IllegalArgumentException("timeout value is negative");
}
}
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0 && millis < Long.MAX_VALUE) {
millis++;
}
join(millis);
}
public final void join() throws InterruptedException {
join(0);
}
當(dāng)調(diào)用join函數(shù)之后主線程和子線程的狀態(tài)切換如下:
- 當(dāng)調(diào)用join()方法時(shí),主線程會(huì)進(jìn)入等待狀態(tài),直到子線程執(zhí)行完畢后才會(huì)繼續(xù)執(zhí)行。此時(shí)主線程的狀態(tài)為WAITING。
- 如果調(diào)用帶參數(shù)的join()方法,主線程會(huì)在等待一段時(shí)間后繼續(xù)執(zhí)行,而不必一直阻塞。在這種情況下,主線程的狀態(tài)為TIMED_WAITING。
- 如果子線程已經(jīng)執(zhí)行完畢,但是主線程還沒有調(diào)用join()方法,則子線程的狀態(tài)為TERMINATED,而主線程的狀態(tài)為RUNNABLE。
- 如果主線程調(diào)用join()方法等待子線程完成執(zhí)行,而子線程拋出了異常,則主線程會(huì)收到異常信息并拋出InterruptedException異常。
測(cè)試代碼如下:
package engineer.concurrent.battle.abasic;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
/**
* ThreadJoinTest
* @author r0ad
* @since 1.0
*/
public class ThreadJoinTest {
public static void main(String[] args) throws InterruptedException{
List<Thread> threadList = IntStream.range(1, 10).mapToObj(ThreadJoinTest::create).toList();
threadList.forEach(Thread::start);
for(Thread thread : threadList){
thread.join();
}
IntStream.range(1, 10).forEach((i)-> {
System.out.println("thread " + Thread.currentThread().getName() + " # "+ i );
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
/**
* 線程創(chuàng)建函數(shù)
* @param index
* @return
*/
private static Thread create(int index) {
return new Thread(() -> {
int i = 0;
while (i++<10) {
System.out.println("thread " + Thread.currentThread().getName() + " # "+ i );
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
}
通過(guò)觀察輸出結(jié)果發(fā)現(xiàn),join之后的線程全部結(jié)束后才會(huì)執(zhí)行輸出main線程的內(nèi)容。
thread Thread-0 # 10
thread Thread-1 # 10
thread Thread-7 # 10
thread Thread-6 # 10
thread main # 1
thread main # 2
thread main # 3
關(guān)閉線程
在JDK 17中,線程停止的情況和函數(shù)有以下幾種:
- 自然結(jié)束:線程執(zhí)行完run()方法后,線程會(huì)自然結(jié)束并進(jìn)入終止?fàn)顟B(tài)。
- 線程被中斷:可以使用Thread類的interrupt()方法來(lái)中斷線程。當(dāng)一個(gè)線程調(diào)用另一個(gè)線程的interrupt()方法時(shí),被調(diào)用線程會(huì)收到一個(gè)中斷信號(hào),并且中斷狀態(tài)會(huì)被設(shè)置為true。中斷狀態(tài)可以通過(guò)Thread類的isInterrupted()方法來(lái)查詢。線程可以在適當(dāng)?shù)臅r(shí)機(jī)檢查中斷狀態(tài),如果中斷狀態(tài)為true,則可以選擇安全地終止線程的執(zhí)行。
- 使用標(biāo)志位停止線程:可以在多線程程序中定義一個(gè)標(biāo)志位,當(dāng)標(biāo)志位為true時(shí),線程停止執(zhí)行。線程可以周期性地檢查該標(biāo)志位,如果標(biāo)志位為true,則主動(dòng)結(jié)束線程的執(zhí)行。
- 使用Thread類的stop()方法(已廢棄):Thread類提供了一個(gè)stop()方法,可以立即停止線程的執(zhí)行。但是這個(gè)方法已經(jīng)被標(biāo)記為不安全和不推薦使用,因?yàn)樗赡軐?dǎo)致線程在不可預(yù)料的位置停止,造成數(shù)據(jù)不一致或其他問(wèn)題。
tips native函數(shù)
Java中的native關(guān)鍵字用于表示某個(gè)方法的實(shí)現(xiàn)是由本地代碼(C、C++等)提供的。這些本地方法可以直接在Java程序中調(diào)用,而無(wú)需了解其底層實(shí)現(xiàn)。
在Java中,使用native關(guān)鍵字定義本地方法時(shí),不需要提供方法體。例如:
public native void myNativeMethod();
在上面的示例中,myNativeMethod()被定義為本地方法,并且沒有提供方法體。在運(yùn)行時(shí),Java虛擬機(jī)將查找本地方法的實(shí)現(xiàn),如果找不到,則會(huì)拋出UnsatisfiedLinkError異常。
要調(diào)用本地方法,需要使用native方法的外部實(shí)現(xiàn)。這通常涉及到將Java代碼與本地代碼庫(kù)進(jìn)行鏈接??梢允褂肑ava本機(jī)接口(JNI)來(lái)實(shí)現(xiàn)這一點(diǎn)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-837904.html
參考
- 《Java高并發(fā)編程詳解:多線程與架構(gòu)設(shè)計(jì)》
- Java Thread Doc
關(guān)于作者
來(lái)自一線全棧程序員nine的八年探索與實(shí)踐,持續(xù)迭代中。歡迎關(guān)注“雨林尋北”或添加個(gè)人衛(wèi)星codetrend(備注技術(shù))。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-837904.html
到了這里,關(guān)于并發(fā)編程Thread的常用API有哪些?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!