# 線程優先級和線程Id
進程有進程的優先級,線程也有優先級,但是優先級這個東西並不是越高的會優先執行,只是在CPU比較忙的時候,優先級較高的線程會有機會獲取更多CPU的執行時間,但是閒時並不會出現這種情況。因此在程序設計中,最好不要將業務與線程的優先級進行強關聯。
/**
* autor:liman
* createtime:2020/6/7
* comment:線程id和線程優先級
*/
@Slf4j
public class ThreadIDAndPriority {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
while(true){
log.info("this is t1 thread");
}
});
t1.setPriority(3);
Thread t2 = new Thread(()->{
while(true){
log.info("this is t2 thread");
}
});
t2.setPriority(8);
t1.start();
t2.start();
}
}
上述代碼多運行幾次,發現有時候t2線程並沒有真正的佔用大部分運行時間。
翻看Thread中關於setPriority的源碼
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
發現其實很簡單,新的線程的優先級不能設置小於1或者大於10,否則會拋出異常,同時線程的優先級不能高於線程組所在的優先級。線程的默認優先級與其父線程的優先級保持一致。
順便說說線程id 通過線程本身的getId方法即可獲取到線程id,線程id對於我們通過jstack排查問題很有幫助。順便提一下,線程id的產生也是一個自增的id
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
sleep和yield
yield並不常用,只是調用yield方法,會將當前線程從RUNNING狀態切換到RUNNABLE狀態,線程不會釋放鎖,yield也是一種啓發式的方法,提醒CPU本線程自願放棄CPU的執行權,如果CPU資源不緊張,則會忽略這種提醒。而sleep方法則是會讓當前線程進入阻塞狀態,但是依舊不釋放鎖。
join
join某個線程A,會使得當前線程B 進入等待,直到線程A結束生命週期,或者到達指定時間,這個時候線程B是處於BLOCKED狀態的,而不是A線程。
可以參考如下示例:
/**
* autor:liman
* createtime:2020/6/7
* comment:join的簡單實例
*/
@Slf4j
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
IntStream.rangeClosed(1,999).forEach(i->log.info("this is t1 thread,i:{}",i));
},"t1");
t1.start();
t1.join();
IntStream.rangeClosed(1,999).forEach(i->log.info("this is main thread,i:{}",i));
}
}
上述代碼會出現主線程和t1線程交替運行的情況,如果加入了t1.join的方法,則主線程會等到t1線程運行完成之後纔開始運行
線程interrupt
線程interrupt是一個比較複雜的內容,關於線程中斷的api主要有以下三個方法
public void interrupt():
public static boolean interrupted();
public boolean isInterrupted();
在正式熟悉這些api方法之前,我們先梳理一下,目前爲止,讓一個線程進入阻塞狀態的方法有如下幾個
1、wait方法
2、sleep方法
3、join方法
同時終止一個線程,並不是直接暴力的方式終結一個線程(stop方法除外)而是將一個線程內部的中斷標誌位置爲true,讓後線程判斷該標誌位是否置爲true,如果置爲true則本身開始進行一些終止線程的操作,後面會詳細總結如何優雅的關閉一個線程的幾種方式
interrupt方法
先直接看實例
@Slf4j
public class ThreadInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while(true) {
log.info("this is t1 thread");
}
},"t1");
t1.start();
Thread.sleep(10l);//主線程休眠10ms
t1.interrupt();//調用t1線程的interrupt方法
}
}
這個時候線程並不會終止,因爲雖然我們調用了t1.interrupt()方法,將t1線程的中斷標誌位置爲true,但是並沒有對這個中斷作出處理,因此我們線程依舊會運行,如果將上述代碼改成如下語句,則t1線程會正常退出
@Slf4j
public class ThreadInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while(true) {
log.info("this is t1 thread");
if(Thread.currentThread().isInterrupted()){//判斷當前線程是否被終止
log.info("interrupted");
break;
}
}
},"t1");
t1.start();
Thread.sleep(10l);
t1.interrupt();
}
}
之前我們梳理了那些方法可以讓線程進入阻塞狀態,這些方法在《Java 高併發編程詳解》一書中被稱爲可中斷方法,如果目標線程在執行這些方法的時候,被外部中斷,則會拋出一個InterruptedException的異常,同時目標線程當前的阻塞狀態被打斷,這個時候我們可以根據拋出的異常來終止目標線程。但是需要說明的是,如果目標線程在執行可中斷方法時被中斷,則中斷標誌位被清除。
/**
* autor:liman
* createtime:2020/6/7
* comment:join的簡單實例
*/
@Slf4j
public class JoinDemo {
Thread t2 = new Thread(){
@Override
public void run() {
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
//這一行輸出爲false,sleep方法清空了t2線程的中斷標誌位
log.info("t2 is interrupted ? {}", this.isInterrupted());
log.error("interrupted");
}
}
};
t2.start();
TimeUnit.SECONDS.sleep(2);
log.info("t1 is interrupt : {}", t2.isInterrupted());
t2.interrupt();
//預留3毫秒給t2調用sleep清空標誌位,不然影響判斷結果
TimeUnit.MILLISECONDS.sleep(3);
log.info("t1 is interrupt : {}", t2.isInterrupted());
}
最終運行結果如下,三個都爲false
如果改爲執行wait方法,則會有一樣的效果
@Slf4j
public class ThreadInterruptWaitDemo {
private static Object MONITOR = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
log.info("this is t1 thread");
while(true){
synchronized (MONITOR) {
try {
MONITOR.wait();
} catch (InterruptedException e) {
log.info("t1 is interrupted ? {}", Thread.currentThread().isInterrupted());
}
}
}
});
t1.start();
TimeUnit.SECONDS.sleep(10);
log.info("t1 is interrupted ? {}",t1.isInterrupted());
t1.interrupt();
TimeUnit.MILLISECONDS.sleep(3);
log.info("t1 is interrupted ? {}",t1.isInterrupted());
}
}
上述代碼運行效果一樣。
interrupted與isInterrupted
interrupted方法是一個靜態方法,isInterrupted方法是一個實例方法,兩者還是有些區別的,調用interrupted方法會直接清除掉線程的中斷標誌位。而isInterrupted則不會。
如何優雅的關閉一個線程
總結完interrupt之後,我們關閉線程其實有一個相對優雅的方式了,就是通過中斷信號關閉線程
通過中斷信號關閉線程
@Slf4j
public class StopThreadGraceful {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while(true){
log.info("t1 will start work");
while(!Thread.currentThread().isInterrupted()){
// doing work;
}
log.info("t1 will exit");
break;
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
log.info("main will stop t1");
t1.interrupt();
}
}
只是有個不好的地方,如果在上述的doing work中我們調用了可中斷方法,會導致線程的正常中斷
@Slf4j
public class StopThreadGraceful {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while(true){
log.info("t1 will start work");
while(!Thread.currentThread().isInterrupted()){
TimeUnit.SECONDS.sleep(1);//這個會導致程序的正常退出,有可能會影響到正常業務。
}
log.info("t1 will exit");
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
log.info("main will stop t1");
t1.interrupt();
}
}
通過自己設置標誌位
由於Thread本的中斷標誌位很有可能會被擦除,因此我們通過自己設置標誌位來中斷線程
public static class MyTask extends Thread{
//設置自己的中斷標誌位
private volatile boolean isClosed = false;
@Override
public void run() {
while(true){
log.info("t1 will start work");
while(!isClosed&&isInterrupted()){//兩者都判斷
// doing work;
}
log.info("t1 will exit");
break;
}
}
//供外部調用的close方法,用於執行標記中斷。
public void close(){
this.isInterrupted();
this.isClosed=true;
}
}