什么是線程
線程(thread) 是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個單一順序的控制流,一個進(jìn)程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。
線程由線程ID,程序計數(shù)器(PC)[用于指向內(nèi)存中的程序指令],寄存器集合[由于存放本地變量和臨時變量]和堆棧[用于存放方法指令和方法參數(shù)等]組成。
線程狀態(tài)
Java線程中,有一個內(nèi)部枚舉類 State,里面定義了Java線程的六種狀態(tài).
| 狀態(tài)名 | 說明 |
|---|---|
| NEW | 初始狀態(tài),線程被構(gòu)建,但是還沒調(diào)用start()方法 |
| RUNNABLE | 處于可運(yùn)行狀態(tài)的線程正在Java虛擬機(jī)中執(zhí)行, 但它可能正在等待來自操作系統(tǒng)(例如處理器)的其他資源。 對應(yīng)操作系統(tǒng)中的就緒和運(yùn)行狀態(tài) |
| BLOCKED | 阻塞狀態(tài), 線程正在處于等待鎖的狀態(tài) |
| WAITING | 等待狀態(tài),表示當(dāng)前線程需要等待其他線程喚醒或中斷. |
| TIMED_WAITING | 超時等待狀態(tài),不同于 WAITING,他可以在指定的時間內(nèi)自行返回 |
| TERMINATED | 終止?fàn)顟B(tài), 表示當(dāng)前線程已經(jīng)執(zhí)行完畢 |

阻塞狀態(tài)是線程阻塞在進(jìn)入synchronized方法或方法塊(等待獲取鎖)的狀態(tài), 但是阻塞在 JUC中Lock接口的線程狀態(tài)確實(shí)等待狀態(tài),因?yàn)镴UC是使用LockSupport相關(guān)方法來實(shí)現(xiàn)阻塞.
線程優(yōu)先級
現(xiàn)代操作系統(tǒng)基本采用時分的形式調(diào)度運(yùn)行的線程,操作系統(tǒng)會分出一個個時間片,線程會分配到若干時間片,當(dāng)線程的時間片用完了就會發(fā)生線程調(diào)度,并等待著下次分配。線程分配到的時間片多少也就決定了線程使用處理器資源的多少,而線程優(yōu)先級就是決定線程需要多或者少分配一些處理器資源的線程屬性。
在Java線程中,通過一個整型成員變量priority來控制優(yōu)先級,優(yōu)先級的范圍從1~10,在線程構(gòu)建的時候可以通過setPriority(int)方法來修改優(yōu)先級,默認(rèn)優(yōu)先級是5,優(yōu)先級高的線程分配時間片的數(shù)量要多于優(yōu)先級低的線程。設(shè)置線程優(yōu)先級時,針對頻繁阻塞(休眠或者I/O操作)的線程需要設(shè)置較高優(yōu)先級,而偏重計算(需要較多CPU時間或者偏運(yùn)算)的線程則設(shè)置較低的優(yōu)先級,確保處理器不會被獨(dú)占。
線程的優(yōu)先級在一些系統(tǒng)中可能不會生效. 即該方法不能保證線程的優(yōu)先級,只能當(dāng)做輔助.
public class Priority {
public static void main(String[] args) throws Exception {
Thread.currentThread().setPriority(10);
Thread t = null;
for (int i = 1; i < 10; i++) {
t = new Thread(new JustWaitRunnable(), "thread priority " + i);
t.setPriority(i);
t.start();
}
t.join();
}
private static class JustWaitRunnable implements Runnable {
@Override
public void run() {
while (true) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
使用 jps指令查找對應(yīng)的java進(jìn)程號.
使用 jstack pid 查看java進(jìn)程具體的信息.
在windows上運(yùn)行的jstack信息:
"main" #1 prio=10 os_prio=2 tid=0x00000000036b3800 nid=0x7fc4 in Object.wait() [0x00000000036af000]
java.lang.Thread.State: WAITING (on object monitor)
"thread priority 9" #18 prio=9 os_prio=2 tid=0x000000001e262000 nid=0x7d7c waiting on condition [0x000000001f51f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 8" #17 prio=8 os_prio=1 tid=0x000000001e261800 nid=0xdfc waiting on condition [0x000000001f41f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 7" #16 prio=7 os_prio=1 tid=0x000000001e25c800 nid=0x7d94 waiting on condition [0x000000001f31f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 6" #15 prio=6 os_prio=0 tid=0x000000001e258000 nid=0x7edc waiting on condition [0x000000001f21e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 5" #14 prio=5 os_prio=0 tid=0x000000001e257000 nid=0x23d4 waiting on condition [0x000000001f11e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 4" #13 prio=4 os_prio=-1 tid=0x000000001e256800 nid=0x7c74 waiting on condition [0x000000001f01f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 3" #12 prio=3 os_prio=-1 tid=0x000000001e253800 nid=0x7a98 waiting on condition [0x000000001ef1e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 2" #11 prio=2 os_prio=-2 tid=0x000000001e23f800 nid=0x7e6c waiting on condition [0x000000001ee1f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 1" #10 prio=1 os_prio=-2 tid=0x000000001e23e800 nid=0x7fdc waiting on condition [0x000000001ed1f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
從上面可以看出,在windows平臺上, java優(yōu)先級和對應(yīng)的內(nèi)核優(yōu)先級的關(guān)系.
| Java線程優(yōu)先級 | 內(nèi)核線程對應(yīng)的優(yōu)先級 |
|---|---|
| 1,2 | -2 |
| 3,4 | -1 |
| 5,6 | 0 |
| 7,8 | 1 |
| 9,10 | 2 |
在mac上的jstack信息
"main" #1 prio=10 os_prio=31 tid=0x00007fea1c800800 nid=0x1103 in Object.wait() [0x00007000016d2000]
java.lang.Thread.State: WAITING (on object monitor)
"thread priority 9" #17 prio=9 os_prio=31 tid=0x00007fea1c83b000 nid=0x5803 waiting on condition [0x0000700002e1a000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 8" #16 prio=8 os_prio=31 tid=0x00007fea1b041800 nid=0xa703 waiting on condition [0x0000700002d17000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 7" #15 prio=7 os_prio=31 tid=0x00007fea1c83a000 nid=0xa803 waiting on condition [0x0000700002c14000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 6" #14 prio=6 os_prio=31 tid=0x00007fea1b040800 nid=0x5503 waiting on condition [0x0000700002b11000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 5" #13 prio=5 os_prio=31 tid=0x00007fea1c839800 nid=0x4503 waiting on condition [0x0000700002a0e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 4" #12 prio=4 os_prio=31 tid=0x00007fea1b040000 nid=0x4403 waiting on condition [0x000070000290b000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 3" #11 prio=3 os_prio=31 tid=0x00007fea1c838800 nid=0x4903 waiting on condition [0x0000700002808000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 2" #10 prio=2 os_prio=31 tid=0x00007fea1c026800 nid=0x4a03 waiting on condition [0x0000700002705000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
"thread priority 1" #9 prio=1 os_prio=31 tid=0x00007fea1b853000 nid=0x4b03 waiting on condition [0x0000700002602000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
線程優(yōu)先級不能作為程序正確性的依賴,因?yàn)椴僮飨到y(tǒng)可以完全不用理會Java 線程對于優(yōu)先級的設(shè)定。
從上面可以看出,在mac系統(tǒng)中, java線程優(yōu)先級的設(shè)置是無效的, 對應(yīng)的系統(tǒng)線程優(yōu)先級都是 31.
另一個例子 :
public class Priority {
private static volatile boolean notStart = true;
private static volatile boolean notEnd = true;
public static void main(String[] args) throws Exception {
List<Job> jobs = new ArrayList<Job>();
for (int i = 1; i < 11; i++) {
Job job = new Job(i);
jobs.add(job);
Thread thread = new Thread(job, "Thread:" + i);
thread.setPriority(i);
thread.start();
}
notStart = false;
Thread.currentThread().setPriority(8);
System.out.println("done.");
TimeUnit.SECONDS.sleep(5);
notEnd = false;
jobs.sort(Comparator.comparingInt(o -> o.jobCount));
for (Job job : jobs) {
System.out.println("Job Priority : " + job.priority + ", Count : " + job.jobCount);
}
}
static class Job implements Runnable {
private final int priority;
private int jobCount;
public Job(int priority) {
this.priority = priority;
}
public void run() {
while (notStart) {
Thread.yield();
}
while (notEnd) {
Thread.yield();
jobCount++;
}
}
}
}
在windows上運(yùn)行:
Job Priority : 2, Count : 102
Job Priority : 1, Count : 318
Job Priority : 3, Count : 10518
Job Priority : 4, Count : 10667
Job Priority : 6, Count : 1113314
Job Priority : 5, Count : 1113323
Job Priority : 7, Count : 1874817
Job Priority : 8, Count : 1875519
Job Priority : 10, Count : 2133667
Job Priority : 9, Count : 2135421
在Mac上運(yùn)行:
Job Priority : 5, Count : 417774
Job Priority : 2, Count : 417997
Job Priority : 3, Count : 418077
Job Priority : 1, Count : 418094
Job Priority : 4, Count : 418203
Job Priority : 9, Count : 418218
Job Priority : 8, Count : 418237
Job Priority : 7, Count : 418299
Job Priority : 6, Count : 418416
Job Priority : 10, Count : 418463
在Android手機(jī)中運(yùn)行:
Job Priority : 2, Count : 1332
Job Priority : 1, Count : 1827
Job Priority : 4, Count : 4250
Job Priority : 3, Count : 24189
Job Priority : 5, Count : 501642
Job Priority : 6, Count : 709992
Job Priority : 7, Count : 1181706
Job Priority : 8, Count : 1906252
Job Priority : 9, Count : 2342730
Job Priority : 10, Count : 2967839
Daemon線程
Daemon線程是一種支持型線程,因?yàn)樗饕挥米鞒绦蛑泻笈_調(diào)度以及支持性工作。這意味著,當(dāng)一個Java虛擬機(jī)中不存在非Daemon線程的時候,Java虛擬機(jī)將會退出。
可以通過調(diào) 用Thread.setDaemon(true)將線程設(shè)置為Daemon線程。Daemon屬性需要在啟動線程之前設(shè)置,不能在啟動線程之后設(shè)置。
public class Daemon {
public static void main(String[] args) {
Thread thread = new Thread(new DaemonRunner());
thread.setDaemon(true);
thread.start();
System.out.println(Thread.currentThread().getName() + " thread run finished");
}
static class DaemonRunner implements Runnable {
@Override
public void run() {
try {
SleepUtils.second(100);
} finally {
System.out.println("DaemonThread run finally");
}
}
}
}
// 運(yùn)行結(jié)果
// main thread run finished
從例子中可以看出,當(dāng)Java虛擬機(jī)中已經(jīng)沒有非Daemon線程,虛擬機(jī)需要退出。Java虛擬機(jī)中的所有Daemon線程都需要立即 終止,因此DaemonRunner立即終止,但是DaemonRunner中的finally塊并沒有執(zhí)行。
在構(gòu)建Daemon線程時,不能依靠finally塊中的內(nèi)容來確保執(zhí)行關(guān)閉或清理資源
的邏輯。因此, io處理,數(shù)據(jù)庫訪問,持有資源需要及時釋放的工作不能放在Daemon線程中處理.
我們開發(fā)者很少會用到它, 它主要是用來做內(nèi)存清理,對象釋放等, 如JVM中GC操作等使用的就是Daemon進(jìn)程.
捕獲線程未處理的異常
線程中, 有兩個方法可以捕獲未處理的異常.Thread.setUncaughtExceptionHandler(UncaughtExceptionHandler)和Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler).
其中 Thread.setUncaughtExceptionHandler(UncaughtExceptionHandler)非靜態(tài)方法, 因此只能處理 該線程中的未處理異常捕獲.
而Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)是靜態(tài)方法, 他可以處理進(jìn)程內(nèi),所有沒有自定義異常處理器的所有未處理異常.
對于這種異常處理, 建議只對 默認(rèn)的處理機(jī)制做增強(qiáng)處理, 而不是完全替換為自己的實(shí)現(xiàn).
應(yīng)用 : Android中獲取奔潰日志
public class LogExceptionHandler implements Thread.UncaughtExceptionHandler {
private final Thread.UncaughtExceptionHandler mParent = Thread.getDefaultUncaughtExceptionHandler();
@Override
public void uncaughtException(Thread t, Throwable e) {
extraHandle(t, e);
if (mParent != null)
mParent.uncaughtException(t, e);
}
private static void extraHandle(Thread t, Throwable e) {
String threadName = t.getName();
String trace = AlException.fullTrace(e);
// todo 保存奔潰日志
System.out.println("Exception in thread \"" + threadName + "\" : " + trace);
}
}
// 在應(yīng)用入口初始化
Thread.setDefaultUncaughtExceptionHandler(new LogExceptionHandler());
線程中斷
中斷可以理解為線程的一個標(biāo)識位屬性,它表示一個運(yùn)行中的線程是否被其他線程進(jìn)行 了中斷操作。中斷好比其他線程對該線程打了個招呼,其他線程通過調(diào)用該線程的interrupt() 方法對其進(jìn)行中斷操作。
理解 interrupt()方法
當(dāng)調(diào)用 interrupt()時:
如果線程在處于Object#wait(), Thread#join()或者Thread#sleep(long)情況時,則interrupt狀態(tài)將會被重置, 即interrupt=false,但會拋出InterruptedException
如果線程通過java.nio.channels.InterruptibleChannel阻塞在IO操作中, 則interrupt=true,且拋出java.nio.channels.ClosedByInterruptException.
如果處于非阻塞狀態(tài), 則interrupt=true,且不會拋出異常,且不會停止線程執(zhí)行.
過期的suspend()、resume()和stop()
為什么官方移除了, 直接停止線程的相關(guān)操作?
不建議使用的原因主要有:以suspend()方法為例,在調(diào)用后,線程不會釋放已經(jīng)占有的資源(比如鎖),而是占有著資源進(jìn)入睡眠狀態(tài),這樣容易引發(fā)死鎖問題。同樣,stop()方法在終結(jié)一個線程時不會保證線程的資源正常釋放,通常是沒有給予線程完成資源釋放工作的機(jī)會,因此會導(dǎo)致程序可能工作在不確定狀態(tài)下。
安全退出線程
除了中斷以外,通常是利用一個boolean變量來控制是否需要停止任務(wù)并終止該線程。
使用boolean變量量退出循環(huán),這個變量最好使用volatile修飾.
public class ExitThread {
public static void main(String[] args) {
// interrupt 的兩種情況
Thread busyThread = new Thread(new BusyRunnable(), "busyThread");
busyThread.start();
Thread sleepThread = new Thread(new SleepRunnable(), "sleepThread");
sleepThread.start();
// boolean 標(biāo)志中斷
TagRunnable tagRunnable = new TagRunnable();
Thread tagThread = new Thread(tagRunnable, "tagThread");
tagThread.start();
SleepUtils.second(2);
// 中斷
busyThread.interrupt();
sleepThread.interrupt();
tagRunnable.stop();
}
private static class BusyRunnable implements Runnable {
@Override
public void run() {
Thread t = Thread.currentThread();
while (!t.isInterrupted()) {
}
System.out.println("busy thread end");
}
}
private static class SleepRunnable implements Runnable {
@Override
public void run() {
Thread t = Thread.currentThread();
while (true) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
break;
}
}
System.out.println("sleep thread end");
}
}
private static class TagRunnable implements Runnable {
private volatile boolean tag = true;
void stop() {
System.out.println("stop:" + Thread.currentThread().getName());
tag = false;
}
@Override
public void run() {
System.out.println("run:" + Thread.currentThread().getName());
while (tag) {
}
System.out.println("tag thread end");
}
}
}
yield()方法
使當(dāng)前線程從執(zhí)行狀態(tài)(運(yùn)行狀態(tài))變?yōu)榭蓤?zhí)行態(tài)(就緒狀態(tài))。cpu會從眾多的可執(zhí)行態(tài)里選擇,也就是說,當(dāng)前也就是剛剛的那個線程還是有可能會被再次執(zhí)行到的,并不是說一定會執(zhí)行其他線程而該線程在下一次中不會執(zhí)行到了。
Java線程中有一個Thread.yield( )方法,很多人翻譯成線程讓步。顧名思義,就是說當(dāng)一個線程使用了這個方法之后,它就會把自己CPU執(zhí)行的時間讓掉,讓自己或者其它的線程重新競爭運(yùn)行。
線程通信
共享內(nèi)存變量-線程安全通信
- synchronized
- volatile
- JUC的Lock
等待/通知機(jī)制
此處講的等待通知機(jī)制,主要是指 Object.wait()/Object.notify()
| 方法名 | 描述 |
|---|---|
| notify() | 通知一個在對象上等待的線程,使其從wait()方法中返回. 返回的前提是 該線程獲得了對象的鎖 |
| notifyAll() | 通知所有等待在該對象上的線程 |
| wait() | 調(diào)用該放的線程進(jìn)入WAITING狀態(tài),只有等待另外線程的通知或者中斷操作才會返回,調(diào)用wait()后,會釋放持有對象的鎖. |
| wait(long) | 等待一段時間,如果超過long時間則直接返回 |
等待/通知機(jī)制是指一個線程A調(diào)用了對象O的wait()方法進(jìn)入等待狀態(tài),而另一個線程B 調(diào)用了對象O的notify()或者notifyAll()方法,線程A收到通知后從對象O的wait()方法返回,進(jìn)而 執(zhí)行后續(xù)操作。上述兩個線程通過對象O來完成交互,而對象上的wait()和notify/notifyAll()的 關(guān)系就如同開關(guān)信號一樣,用來完成等待方和通知方之間的交互工作。
模型
等待方遵循如下原則:
- 獲取對象的鎖。
- 如果條件不滿足,那么調(diào)用對象的wait()方法,被通知后仍要檢查條件。
- 條件滿足則執(zhí)行對應(yīng)的邏輯。
synchronized(object) {
while(條件不滿足) {
object.wait();
}
// todo 條件滿足對應(yīng)的處理
}
通知方遵循如下原則:
- 獲得對象的鎖。
- 改變條件。
- 通知所有等待在對象上的線程。
synchronized(object) {
// todo 改變條件
object.notify();
}