什么是線程
線程(thread) 是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中售貌,是進(jìn)程中的實(shí)際運(yùn)作單位伞鲫。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流拗秘,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)草穆。
線程由線程ID灌灾,程序計(jì)數(shù)器(PC)[用于指向內(nèi)存中的程序指令],寄存器集合[由于存放本地變量和臨時(shí)變量]和堆棧[用于存放方法指令和方法參數(shù)等]組成悲柱。
線程狀態(tài)
Java線程中,有一個(gè)內(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 | 超時(shí)等待狀態(tài),不同于 WAITING ,他可以在指定的時(shí)間內(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)基本采用時(shí)分的形式調(diào)度運(yùn)行的線程豌鸡,操作系統(tǒng)會分出一個(gè)個(gè)時(shí)間片嘿般,線程會分配到若干時(shí)間片,當(dāng)線程的時(shí)間片用完了就會發(fā)生線程調(diào)度直颅,并等待著下次分配博个。線程分配到的時(shí)間片多少也就決定了線程使用處理器資源的多少,而線程優(yōu)先級就是決定線程需要多或者少分配一些處理器資源的線程屬性功偿。
在Java線程中盆佣,通過一個(gè)整型成員變量priority來控制優(yōu)先級往堡,優(yōu)先級的范圍從1~10,在線程構(gòu)建的時(shí)候可以通過setPriority(int)方法來修改優(yōu)先級共耍,默認(rèn)優(yōu)先級是5虑灰,優(yōu)先級高的線程分配時(shí)間片的數(shù)量要多于優(yōu)先級低的線程。設(shè)置線程優(yōu)先級時(shí)痹兜,針對頻繁阻塞(休眠或者I/O操作)的線程需要設(shè)置較高優(yōu)先級穆咐,而偏重計(jì)算(需要較多CPU時(shí)間或者偏運(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.
另一個(gè)例子 :
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)一個(gè)Java虛擬機(jī)中不存在非Daemon線程的時(shí)候屈暗,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線程時(shí)肮之,不能依靠finally塊中的內(nèi)容來確保執(zhí)行關(guān)閉或清理資源
的邏輯。因此, io處理,數(shù)據(jù)庫訪問,持有資源需要及時(shí)釋放的工作不能放在Daemon線程中處理.
我們開發(fā)者很少會用到它, 它主要是用來做內(nèi)存清理,對象釋放等, 如JVM中GC操作等使用的就是Daemon進(jìn)程.
捕獲線程未處理的異常
線程中, 有兩個(gè)方法可以捕獲未處理的異常.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());
線程中斷
中斷可以理解為線程的一個(gè)標(biāo)識位屬性卜录,它表示一個(gè)運(yùn)行中的線程是否被其他線程進(jìn)行 了中斷操作戈擒。中斷好比其他線程對該線程打了個(gè)招呼,其他線程通過調(diào)用該線程的interrupt()
方法對其進(jìn)行中斷操作艰毒。
理解 interrupt()
方法
當(dāng)調(diào)用 interrupt()
時(shí):
如果線程在處于Object#wait()
, Thread#join()
或者Thread#sleep(long)
情況時(shí),則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é)一個(gè)線程時(shí)不會保證線程的資源正常釋放,通常是沒有給予線程完成資源釋放工作的機(jī)會西乖,因此會導(dǎo)致程序可能工作在不確定狀態(tài)下狐榔。
安全退出線程
除了中斷以外坛增,通常是利用一個(gè)boolean變量來控制是否需要停止任務(wù)并終止該線程。
使用boolean變量量退出循環(huán),這個(gè)變量最好使用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)前也就是剛剛的那個(gè)線程還是有可能會被再次執(zhí)行到的庵楷,并不是說一定會執(zhí)行其他線程而該線程在下一次中不會執(zhí)行到了罢艾。
Java線程中有一個(gè)Thread.yield( )
方法,很多人翻譯成線程讓步尽纽。顧名思義咐蚯,就是說當(dāng)一個(gè)線程使用了這個(gè)方法之后,它就會把自己CPU執(zhí)行的時(shí)間讓掉蜓斧,讓自己或者其它的線程重新競爭運(yùn)行仓蛆。
線程通信
共享內(nèi)存變量-線程安全通信
- synchronized
- volatile
- JUC的Lock
等待/通知機(jī)制
此處講的等待通知機(jī)制,主要是指 Object.wait()/Object.notify()
方法名 | 描述 |
---|---|
notify() | 通知一個(gè)在對象上等待的線程,使其從wait()方法中返回. 返回的前提是 該線程獲得了對象的鎖 |
notifyAll() | 通知所有等待在該對象上的線程 |
wait() | 調(diào)用該放的線程進(jìn)入WAITING 狀態(tài),只有等待另外線程的通知或者中斷操作才會返回,調(diào)用wait()后,會釋放持有對象的鎖. |
wait(long) | 等待一段時(shí)間,如果超過long時(shí)間則直接返回 |
等待/通知機(jī)制是指一個(gè)線程A調(diào)用了對象O的wait(
)方法進(jìn)入等待狀態(tài)睁冬,而另一個(gè)線程B 調(diào)用了對象O的notify()
或者notifyAll()
方法挎春,線程A收到通知后從對象O的wait()
方法返回,進(jìn)而 執(zhí)行后續(xù)操作豆拨。上述兩個(gè)線程通過對象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();
}