多線程在Java中無處不在粮揉,在上一篇(Java線程概念理解)中我們看到就算是一個最簡單的Java類中也涉及到了多線程,大家可能會疑惑吟策,為什么一個這么簡單的Java類中貌夕,卻啟動了那么多“無關”的線程,Java是不是將簡單的問題搞復雜了呢虏等?答案當然是否定了弄唧,這是因為正確的使用多線程能夠?qū)⒑臅r的處理大大的縮減時間,能夠讓用戶的體驗更加友好霍衫。使用多線程的主要原因有以下幾點:
- 更多的處理器核心
現(xiàn)代處理器的核數(shù)越來越多候引,以及超線程越來越廣泛的使用,現(xiàn)代計算機越來越擅長并行計算敦跌。我們知道一個程序作為一個進程在運行澄干,而一個進程可以創(chuàng)建多個線程,而一個線程在一個時刻只能運行在一個處理器核心上柠傍,試想一下一個單線程程序在運行時只能使用一個處理器核心麸俘,那么再多的處理器核心也無法提升該程序的執(zhí)行效率。
- 更快的響應時間
多線程能夠給用戶更快的響應時間惧笛,例如我們在做一些復雜的業(yè)務邏輯的時候从媚,我們先可以給用戶一個反饋,然后再將一些數(shù)據(jù)一致性不強的業(yè)務邏輯派發(fā)給其它線程去處理患整,這樣一來就大大縮短了響應時間拜效,提升了用戶體驗喷众。
- 更好的編程模型
Java為多線程提供了良好的編程模型,使開發(fā)者能夠更加專注于問題的解決紧憾,即為所遇到的問題建立合適的模型到千,而不是絞盡腦汁的如何將其多線程化。
1 線程優(yōu)先級
線程優(yōu)先級從低到高依次為1~10赴穗,在創(chuàng)建線程是可以使用setPriority(int newPriority)方法來設置線程等的優(yōu)先級憔四,默優(yōu)先級位是5。線程的優(yōu)先級控制的是線程獲取時間片段的多少般眉,也就是說優(yōu)先級高的線程分配時間片的數(shù)量多于優(yōu)先級低的線程了赵。但是這也不是絕對的,因為不同的JVM和操作系統(tǒng)上煤篙,線程的規(guī)劃會存在這差異斟览,有些操作系統(tǒng)甚至會忽略線程優(yōu)先級的設置毁腿,因此不要將線程的優(yōu)先級作為線程的運行順序以及程序的正確性的依賴辑奈。
線程優(yōu)先級設置
線程優(yōu)先級在Thread類中的定義如下:
/**
* 線程可以擁有的最小優(yōu)先級
*/
public final static int MIN_PRIORITY = 1;
/**
* 分配個線程的默認優(yōu)先級
*/
public final static int NORM_PRIORITY = 5;
/**
* 線程可以擁有的最大優(yōu)先級
*/
public final static int MAX_PRIORITY = 10;
public final void setPriority(int newPriority) {
//線程組
ThreadGroup g;
checkAccess();
//優(yōu)先級范圍檢查
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if ((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
//設置線程優(yōu)先級
setPriority0(priority = newPriority);
}
}
// 最終調(diào)用本地方法設置優(yōu)先級
private native void setPriority0(int newPriority);
接下來我們看下線程優(yōu)先級的特性:
- 線程優(yōu)先級的繼承性:如果一個線程A啟動了線程B,那么線程B的優(yōu)先級將會和線程A 的一致(線程B不顯示的設置線程優(yōu)先級)已烤。
- 線程優(yōu)先級的隨機性:優(yōu)先級高的線程并不一定會先于優(yōu)先級低的線程執(zhí)行鸠窗。
下面我們來驗證下線程優(yōu)先級的這些特性:
public class ThreadPriority {
public static void main(String[] args) {
Thread1 t1 = new Thread1();
t1.start();
System.out.println("main的線程優(yōu)先級:" + Thread.currentThread().getPriority());
}
}
class Thread1 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "優(yōu)先級:" + Thread.currentThread().getPriority());
}
}
output:
main的線程優(yōu)先級:5
Thread-0優(yōu)先級:5
public class ThreadPriority {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
PriorityThread t = new PriorityThread("線程優(yōu)先級" + i);
t.setPriority(i);
t.start();
}
}
}
class PriorityThread extends Thread {
public PriorityThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開始執(zhí)行....");
long beginTime = System.currentTimeMillis();
long addResult = 0;
for (int j = 0; j < 10; j++) {
for (int i = 0; i < 50000; i++) {
Random random = new Random();
random.nextInt();
addResult = addResult + i;
}
}
long endTime = System.currentTimeMillis();
System.out.println("☆☆☆☆☆" + Thread.currentThread().getName() + " use time=" + (endTime - beginTime));
}
}
output:
線程優(yōu)先級9開始執(zhí)行....
線程優(yōu)先級10開始執(zhí)行....
線程優(yōu)先級7開始執(zhí)行....
線程優(yōu)先級8開始執(zhí)行....
線程優(yōu)先級5開始執(zhí)行....
線程優(yōu)先級6開始執(zhí)行....
線程優(yōu)先級4開始執(zhí)行....
線程優(yōu)先級3開始執(zhí)行....
☆☆☆☆☆線程優(yōu)先級10 use time=157
線程優(yōu)先級2開始執(zhí)行....
☆☆☆☆☆線程優(yōu)先級9 use time=226
☆☆☆☆☆線程優(yōu)先級7 use time=273
☆☆☆☆☆線程優(yōu)先級8 use time=274
☆☆☆☆☆線程優(yōu)先級5 use time=354
☆☆☆☆☆線程優(yōu)先級6 use time=369
線程優(yōu)先級1開始執(zhí)行....
☆☆☆☆☆線程優(yōu)先級3 use time=572
☆☆☆☆☆線程優(yōu)先級4 use time=580
☆☆☆☆☆線程優(yōu)先級1 use time=223
☆☆☆☆☆線程優(yōu)先級2 use time=455
output:
線程優(yōu)先級7開始執(zhí)行....
線程優(yōu)先級8開始執(zhí)行....
線程優(yōu)先級9開始執(zhí)行....
線程優(yōu)先級10開始執(zhí)行....
線程優(yōu)先級5開始執(zhí)行....
☆☆☆☆☆線程優(yōu)先級9 use time=170
☆☆☆☆☆線程優(yōu)先級10 use time=185
線程優(yōu)先級6開始執(zhí)行....
☆☆☆☆☆線程優(yōu)先級7 use time=206
☆☆☆☆☆線程優(yōu)先級8 use time=228
線程優(yōu)先級1開始執(zhí)行....
☆☆☆☆☆線程優(yōu)先級5 use time=262
線程優(yōu)先級3開始執(zhí)行....
☆☆☆☆☆線程優(yōu)先級6 use time=84
線程優(yōu)先級2開始執(zhí)行....
線程優(yōu)先級4開始執(zhí)行....
☆☆☆☆☆線程優(yōu)先級4 use time=95
☆☆☆☆☆線程優(yōu)先級3 use time=112
☆☆☆☆☆線程優(yōu)先級2 use time=135
☆☆☆☆☆線程優(yōu)先級1 use time=230
從上面2個例子,我們就驗證了線程優(yōu)先級的繼承性和隨機性胯究。
2稍计、線程狀態(tài)
Java線程在運行的生命周期中可能處于以下幾種狀態(tài),在給定的時刻裕循,線程只能處于其中一種狀態(tài)臣嚣。線程狀態(tài)如下:
- NEW:初始狀態(tài),線程被構(gòu)建剥哑,但是還沒有調(diào)用start()方法硅则。
- RUNNABLE:運行狀態(tài),Java線程將操作系統(tǒng)中就緒和運行兩種狀態(tài)籠統(tǒng)的稱作“運行中”株婴。
- BLOCKED:阻塞狀態(tài)怎虫,表示線程阻塞于鎖。
- WAITING:等待狀態(tài)困介,表示線程進入等待狀態(tài)大审,進入該狀態(tài)表示當前線程需要等待其他線程做出一些特定動作(通知或中斷)
- TIME_WAITING:超時等待狀態(tài),該狀態(tài)不同于WAITING座哩,它是可以在指定的時間自行返回的徒扶。
- TERMINATED:終止狀態(tài),表示當前線程已經(jīng)執(zhí)行完畢根穷。
我們用一個示例來深入的理解現(xiàn)在的狀態(tài)酷愧,示例代碼如下:
public class ThreadState {
public static void main(String[] args) {
new Thread(new TimeWaiting(), "TimeWaitingThread").start();
new Thread(new Waiting(), "WaitingThread").start();
// 使用兩個Blocked線程驾诈,一個獲取鎖成功,另一個被阻塞
new Thread(new Blocked(), "BlockedThread-1").start();
new Thread(new Blocked(), "BlockedThread-2").start();
}
// 該線程不斷地進行睡眠
static class TimeWaiting implements Runnable {
@Override
public void run() {
while (true) {
try {
SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 該線程在Waiting.class實例上等待
static class Waiting implements Runnable {
@Override
public void run() {
while (true) {
synchronized(Waiting.class) {
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 該線程在Blocked.class實例上加鎖后溶浴,不會釋放該鎖
static class Blocked implements Runnable {
public void run() {
synchronized(Blocked.class) {
while (true) {
try {
SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
打開終端乍迄,輸入“jps”,輸出如下:
C:\Users\Administrator>jps
15680 Jps
16160 ThreadState
10772
15492 Launcher
3196 Launcher
可以看到運行示例對應的進程ID是16160,接著在輸入“jstack 16160”,部分輸入如下
C:\Users\Administrator>jstack 16160
// BlockedThread-2 線程阻塞在獲取Blocked.class示例的鎖上
"BlockedThread-2" #14 prio=5 os_prio=0 tid=0x0000000019399000 nid=0x1dec waiting for monitor entry [0x000000001a00f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.zzw.juc.thread.ThreadState$Blocked.run(ThreadState.java:54)
- waiting to lock <0x00000000d87c86a0> (a java.lang.Class for com.zzw.juc.thread.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
// BlockedThread-1線程獲取到了Blocked.class的鎖
"BlockedThread-1" #13 prio=5 os_prio=0 tid=0x0000000019394000 nid=0x3e58 waiting on condition [0x0000000019f0f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.zzw.juc.thread.ThreadState$Blocked.run(ThreadState.java:54)
- locked <0x00000000d87c86a0> (a java.lang.Class for com.zzw.juc.thread.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
// WaitingThread線程在Waiting實例上等待
"WaitingThread" #12 prio=5 os_prio=0 tid=0x00000000193a0000 nid=0x3ed0 in Object.wait() [0x0000000019e0f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d87c5910> (a java.lang.Class for com.zzw.juc.thread.ThreadState$Waiting)
at java.lang.Object.wait(Object.java:502)
at com.zzw.juc.thread.ThreadState$Waiting.run(ThreadState.java:40)
- locked <0x00000000d87c5910> (a java.lang.Class for com.zzw.juc.thread.ThreadState$Waiting)
at java.lang.Thread.run(Thread.java:748)
// TimeWaitingThread線程處于超時等待
"TimeWaitingThread" #11 prio=5 os_prio=0 tid=0x0000000019393000 nid=0x38d8 waiting on condition [0x0000000019d0e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.zzw.juc.thread.ThreadState$TimeWaiting.run(ThreadState.java:27)
at java.lang.Thread.run(Thread.java:748)
通過上面的示例士败,我們大概了解了Java程序運行中線程狀態(tài)的具體含義闯两。線程在自身的生命周期中,并不是固定地處于某個狀態(tài)谅将,而是隨著代碼的執(zhí)行在不同的狀態(tài)之間進行切換漾狼,Java線程狀態(tài)變遷如下圖所示:
從上圖我們可以看到,線程創(chuàng)建后調(diào)用start()方法開始執(zhí)行饥臂。當線程執(zhí)行了waite()方法之后逊躁,線程進入了等待狀態(tài),進入到等待狀態(tài)的線程需要其它線程的通知才能返回到運行狀態(tài)隅熙。超時等待狀態(tài)相當于在等待狀態(tài)上增加了超時時間的限制稽煤,也就是說當超時時間到達是線程會從超時等待狀態(tài)返回到運行狀態(tài)。當線程在競爭鎖失敗時囚戚,會進入阻塞狀態(tài)酵熙,當阻塞狀態(tài)的線程競爭鎖成功時,將進入運行狀態(tài)驰坊。線程在執(zhí)行完run()方法之后進入終止狀態(tài)匾二。
注意: 這里說的競爭鎖失敗,是線程在進入synchronized關鍵字修飾的方法或者代碼拳芙。而不是java并發(fā)包下的Lock接口實現(xiàn)的鎖察藐。在Lock鎖競爭失敗,線程進入的是等待狀態(tài)舟扎,因為java并發(fā)包中Lock接口對于阻塞的實現(xiàn)均使用了LockSupport類中的相關方法分飞。
3 Deamon線程
Deamon線程是一種支持型線程,它主要被用作程序中后臺調(diào)度已經(jīng)支持性工作浆竭。Deamon線程在主線程結(jié)束后將被直接關閉浸须。可以通過Thread.setDaemon(true)方法將線程轉(zhuǎn)換為Deamon線程邦泄。設置Deamon屬性必須在線程啟動之前設置删窒,線程啟動后設置的Deamon屬性將不起作用。