線程
線程(Thread)是java程序運(yùn)行的基本調(diào)度單元; 在進(jìn)行JUC的源碼分析之前, 想回顧一下Thread的基本屬性, 及方法;
1. 線程 State:
/**
* Created by xjk on 1/12/17.
*/
public enum State {
/** 線程剛剛創(chuàng)建時的狀態(tài), 馬上到 RUNNABLE */
NEW,
/** 線程初始化OK, 開始執(zhí)行任務(wù)(run) */
RUNNABLE,
/**
* 阻塞狀態(tài), 千萬別和WAITING狀態(tài)混淆
* 這種狀態(tài)是線程在等待 JVM monitor lock(通俗一點(diǎn) 就是等待執(zhí)行 synchronous 里面的代碼)
* 這和 LockSupport 沒半毛錢關(guān)系
*/
BLOCKED,
/**
* 線程的等待狀態(tài), 導(dǎo)致線程進(jìn)入這種狀態(tài)通常是下面三個方法
* 1. Object.wait()
* 2. Thread.join()
* 3. LockSupport.park()
*/
WAITING,
/**
* 這也是線程的等待狀態(tài), 和WAITING差不多, 只是這個有timeout而已, 通常由下面四種方法導(dǎo)致
* 1. Object.wait(long timeout)
* 2. Thread.join(long timeout)
* 3. LockSupport.parkNanos(long timeout)
* 4. LockSupport.parkUntil(long timeout)
*/
TIMED_WAITING,
/**
* 線程執(zhí)行ok
*/
TERMINATED
}
這些狀態(tài)何時有用, 我又何時見到它們呢? 對, 一般線程出問題就會想到它們了(比如 程序死鎖, 直接運(yùn)行 jstack 查看線程堆棧, 就能大體判斷問題出在哪個線程, 哪段代碼)
2. 常用方法
/** 等待 */
Object.wait();
/** 通知 */
Object.notify();
/** 懸掛 */
Thread.suspend();
/** 重用 */
Thread.resume();
/** 等待x線程執(zhí)行后, 當(dāng)前線程再執(zhí)行 */
Thread.join();
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
* 這段英語大體意思: 給調(diào)度器發(fā)送信息, 當(dāng)前線程推出CPU調(diào)度(這個不是指當(dāng)前線程不執(zhí)行任務(wù))
* 調(diào)用這個方法后, 當(dāng)前線程會先推出任務(wù)調(diào)度, 然后再重新?lián)寠ZCPU, 但能不能搶到就不一定了
* 通產(chǎn)用于, 當(dāng)前線程占用較多資源, 但任務(wù)又不緊急的情況(concurrent包中的源碼會提及)
*/
Thread.yield();
這幾個是線程的基本用法, 但現(xiàn)在用得比較少, 為啥?
- jdk中有了更好用的 concurrent 包開進(jìn)行多線程開發(fā)
- 使用這些方法有時會出現(xiàn)奇怪的事件(尤其和 concurrent 包中的工具類混用, 匪夷所思)
ps: 若想用這些方法來寫 Future, Promise 等工具類 可以 參考Netty 4.x系列
3. 線程中斷
線程中斷是java中線程協(xié)作的重要機(jī)制, 而所謂的中斷其實(shí)只是給線程設(shè)置中斷標(biāo)示, 并且喚醒正在waiting狀態(tài)的線程;
線程中斷相關(guān)的主要有三個方法:
/**
* 作用: 中斷線程,
* 若線程 sleep 或 wait 時調(diào)用此方法,
* 則拋出 InterruptedException 異常, 并且會清除中斷標(biāo)記
* (ps 重點(diǎn)來了, 若通過 LockSupport阻塞線程, 則不會拋出異常, 并且不會清除線程的中斷標(biāo)記, 這在 concurrent 包里面充分利用了這個機(jī)制)
*
* 比如先通過 LockSupport.park(this) 來中斷, 而后其他線程釋放lock時, 喚醒這個線程, 這時再調(diào)用 Thread.interrupted() 返回中斷標(biāo)示(調(diào)用此方法會清除中斷標(biāo)示)
* 這時外面的函數(shù)會根據(jù) parkAndCheckInterrupt() 函數(shù)的返回值判斷線程的喚醒是被 interrupted 還是正常的喚醒(LockSupport.unpark()) 來決定后續(xù)的策略
* private final boolean parkAndCheckInterrupt() {
* LockSupport.park(this);
* return Thread.interrupted();
* }
*/
Thread.interrupt();
/**
* 判斷當(dāng)前的線程是否中斷, 返回 true/false
*/
Thread.isInterrupted();
/**
* 判斷當(dāng)前的線程是否中斷, 并且清除中斷標(biāo)示(注意這里是 interrupted, 和上面的 interrupt是不一樣的)
*/
Thread.interrupted();
線程的中斷是 Java 并發(fā)開發(fā)中非常重要的機(jī)制, concurrent 包中的很多工具類的方法都是通過這個機(jī)制來安全退出;
下面來段代碼示例一下:
import org.apache.log4j.Logger;
import java.util.concurrent.locks.LockSupport;
/**
* Created by xjk on 1/13/17.
*/
public class ThreadTest {
private static final Logger logger = Logger.getLogger(ThreadTest.class);
public static void main(String[] args) throws Exception{
Thread t1 = new Thread(){
@Override
public void run() {
while(true){
if(Thread.currentThread().isInterrupted()){
logger.info("線程中斷, 退出loop");
break;
}
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
logger.info("線程在 waiting 狀態(tài)時收到中斷信息");
logger.info("此時線程中斷標(biāo)示: " + Thread.currentThread().isInterrupted());
// 再次點(diǎn)用線程中斷, 這時就又有中斷標(biāo)示
Thread.currentThread().interrupt();
}
Thread.yield();
}
logger.info("1. 此時線程中斷標(biāo)示: " + Thread.currentThread().isInterrupted());
// 再次調(diào)用程序阻塞, 看看是否有用
LockSupport.park(this);
logger.info("2. 此時線程中斷標(biāo)示: " + Thread.currentThread().isInterrupted());
try {
Thread.currentThread().sleep(5*1000);
} catch (InterruptedException e) {
logger.info("在線程中斷時調(diào)用sleep 拋異常");
e.printStackTrace();
}
logger.info("3. 此時線程中斷標(biāo)示: " + Thread.currentThread().interrupted());
}
};
t1.start();
Thread.sleep(2 *1000);
t1.interrupt();
}
}
這段代碼主要測試線程中斷的幾個函數(shù)功能, 建議自己寫一下, 在運(yùn)行程序時, 會發(fā)現(xiàn) 程序中的'LockSupport.park(this);' 沒起作用, 而且還沒清除中斷標(biāo)記; 但此時調(diào)用 Thread.sleep(long timeout), 則會因?yàn)橹袛鄻?biāo)示的存在而拋出異常(拋出異常后中斷標(biāo)示也被清除);
執(zhí)行結(jié)果
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.lami.tuomatuo.search.base.concurrent.thread.ThreadTest$1.run(ThreadTest.java:26)
[2017-01-13 22:23:03,903] INFO Thread-0 (ThreadTest.java:29) - 線程在 waiting 狀態(tài)時收到中斷信息
[2017-01-13 22:23:03,906] INFO Thread-0 (ThreadTest.java:30) - 此時線程中斷標(biāo)示: false
[2017-01-13 22:23:03,907] INFO Thread-0 (ThreadTest.java:21) - 線程中斷, 退出loop
[2017-01-13 22:23:03,907] INFO Thread-0 (ThreadTest.java:37) - 1. 此時線程中斷標(biāo)示: true
[2017-01-13 22:23:03,908] INFO Thread-0 (ThreadTest.java:41) - 2. 此時線程中斷標(biāo)示: true
[2017-01-13 22:23:03,909] INFO Thread-0 (ThreadTest.java:45) - 在線程中斷時調(diào)用sleep 拋異常
[2017-01-13 22:23:03,909] INFO Thread-0 (ThreadTest.java:50) - 3. 此時線程中斷標(biāo)示: false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.lami.tuomatuo.search.base.concurrent.thread.ThreadTest$1.run(ThreadTest.java:43)
4. Daemon進(jìn)程
Daemon線程: 守護(hù)線程通常是在后臺默默干一些苦活, 比如垃圾回收; 守護(hù)線程有個特點(diǎn), 就是當(dāng)那些daemon=false的線程都退出, 則daemon也退出
直接上代碼:
import org.apache.log4j.Logger;
/**
* Created by xjk on 1/13/17.
*/
public class DaemonThreadTest {
private static final Logger logger = Logger.getLogger(DaemonThreadTest.class);
static Thread t1 = new Thread(){
@Override
public void run() {
while(true){
logger.info("I am alive");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
logger.info("我要退出程序了");
}
};
public static void main(String[] args) throws Exception{
t1.setDaemon(true);
t1.start();
Thread.sleep(3 * 1000);
logger.info("main 方法執(zhí)行OK");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
logger.info("DaemonThreadTest 退出");
}
}
線程t1 daemon=true, 所以在主線程退出后, 自己也退出, 自己在運(yùn)行時,可以將 "t1.setDaemon(true)" 注釋掉再看看效果
ps:
- finalize在java中不一定會執(zhí)行
- daemon 默認(rèn)值是 false
console:
[2017-01-13 22:43:27,605] INFO Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:28,612] INFO Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:29,615] INFO Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:30,606] INFO main (DaemonThreadTest.java:38) - main 方法執(zhí)行OK
Process finished with exit code 0
總結(jié): 線程中斷機(jī)制在整個并發(fā)編程中起著非常大的作用, 尤其和 LockSupport 配合使用
參考資料:
skywang12345 線程 interrupt
zhanjindong LockSupport和Interrupt (這篇寫得相當(dāng)好??)