概述
先了解一下基本概念。線程是操作系統(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ù)(多核CPU下才能實(shí)現(xiàn)線程并行)。在單核CPU中蛤奥,多線程的并發(fā)從宏觀角度看佳镜,是多個(gè)線程同時(shí)執(zhí)行,但是從微觀角度看凡桥,多線程還是需要通過CPU的時(shí)間片切換來實(shí)現(xiàn)的蟀伸,同一時(shí)間是無法做到多個(gè)線程在單個(gè)CPU中執(zhí)行的。在多核CPU中唬血,才能實(shí)現(xiàn)多個(gè)線程并行執(zhí)行望蜡。在實(shí)際應(yīng)用場景中,具體使用單線程還是多線程拷恨, 需要根據(jù)實(shí)際場景來做衡量脖律,并不是所有場景都更適合多線程。
新建線程的幾種方式
Java中新建線程有三種方式:繼承Tread類腕侄;實(shí)現(xiàn)Runnable接口小泉;通過callable和Future實(shí)現(xiàn);
繼承Thread
- 定義Thread類的子類冕杠,并重寫run方法微姊,該類中run方法的方法體就代表了該線程要執(zhí)行的內(nèi)容。
- 創(chuàng)建Thread子類的實(shí)例分预,即創(chuàng)建線程對象兢交。
- 調(diào)用線程對象的start()方法啟動(dòng)線程。
public static void main(String[] args) {
//第一種創(chuàng)建線程實(shí)例的方式
Thread threadDemo1 = new ThreadDemo1();
threadDemo1.start();
//第二種創(chuàng)建線程實(shí)例的方式
Thread threadDemo2 = new Thread(){
@Override
public void run(){
System.out.println("新建線程Demo2笼痹!");
}
};
threadDemo2.start();
}
static class ThreadDemo1 extends Thread{
@Override
public void run() {
System.out.println("繼承Thread類配喳,新建線程Demo1酪穿!");
}
}
實(shí)現(xiàn)Runnable
- 實(shí)現(xiàn)Runnable接口,重寫該接口的run()方法晴裹。
- 創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例被济,并將此實(shí)例作為創(chuàng)建Thread的Target來創(chuàng)建thread的實(shí)例,該tread實(shí)例才是真正的線程象涧团。
- 調(diào)用thread實(shí)例的start()方法啟動(dòng)線程只磷。
public static void main(String[] args) {
//第一種實(shí)現(xiàn)方式
Runnable runnable = new RunnableDemo1();
Thread runnableDemo1 = new Thread(runnable);
runnableDemo1.start();
//第二種實(shí)現(xiàn)方式
Thread runnableDemo2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"實(shí)現(xiàn)Runnable接口,新建RunnableDemo2!");
}
});
runnableDemo2.start();
}
static class RunnableDemo1 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "實(shí)現(xiàn)Runnable接口泌绣,新建線程RunnableDemo1!");
}
}
通過Callable和Future
- 實(shí)現(xiàn)Callable接口钮追,并重寫改接口的call()方法,call()方法的方法體即該類的執(zhí)行內(nèi)容赞别。
- 創(chuàng)建Callable接口實(shí)現(xiàn)類的實(shí)例畏陕,并使用FutureTask來包裝callable實(shí)例,該FutureTask封裝了callable實(shí)例的call()方法的返回值仿滔。(FutureTask是一個(gè)包裝器惠毁,它通過接受Callable來創(chuàng)建,它同時(shí)實(shí)現(xiàn)了Future和Runnable接口崎页。)
- 使用FutureTask實(shí)例作為thread的target創(chuàng)建線程鞠绰。
- 調(diào)用tread的start()方法啟動(dòng)線程。
public static void main(String[] args) {
CallableDemo1 callableDemo1 = new CallableDemo1();
FutureTask<Integer> futureTask = new FutureTask<>(callableDemo1);
Thread thread = new Thread(futureTask);
thread.start();
try {
Integer i = futureTask.get();
System.out.println(Thread.currentThread().getName() + "獲取到線程的返回值為:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
static class CallableDemo1 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int i = 100;
System.out.println(Thread.currentThread().getName() +" " + i);
return i;
}
}
三種創(chuàng)建方式的比較
- 由于Java是單繼承多實(shí)現(xiàn)的飒焦,所以盡量使用接口實(shí)現(xiàn)的方式創(chuàng)建線程蜈膨,這樣還可以繼承其余的類
- callable接口實(shí)現(xiàn)方式,較其余兩種相對復(fù)雜牺荠,但是該實(shí)現(xiàn)方式線程執(zhí)行后有返回值翁巍,其余方式?jīng)]有
- Thread類實(shí)現(xiàn)了Runnable接口
線程狀態(tài)的轉(zhuǎn)換
我們通過查看Thread類的源碼,發(fā)現(xiàn)線程只有六種狀態(tài):NEW(新建)休雌、RUNNABLE(運(yùn)行)灶壶、BLOCKED(阻塞裝填)、WAITING(等待狀態(tài))杈曲、TIMED_WAITING(超時(shí)等待狀態(tài))驰凛、TERMINATED(終止?fàn)顟B(tài)),具體源碼如下:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
從源代碼注釋中可以整理出線程狀態(tài)轉(zhuǎn)換的過程。當(dāng)一個(gè)線程創(chuàng)建之后,就處于NEW(新建狀態(tài)),調(diào)用Thread.start()后蕴侧,線程并不會(huì)立馬執(zhí)行,而是進(jìn)入REDAY(就緒狀態(tài))遭顶,等待系統(tǒng)調(diào)度該線程之后,進(jìn)入RUNNING(運(yùn)行中狀態(tài))锻梳。
CPU執(zhí)行每個(gè)線程是有一個(gè)時(shí)間限制的伍茄,這個(gè)時(shí)間段被稱為時(shí)間片间唉,當(dāng)一個(gè)時(shí)間片結(jié)束后绞灼,線程仍在執(zhí)行中利术,那么系統(tǒng)會(huì)將線程重新置為REDAY(就緒狀態(tài)),或者在線程運(yùn)行時(shí)呈野,調(diào)用Thread.yeild()方法,該線程一樣會(huì)被置為READY狀態(tài)印叁,等待系統(tǒng)再次調(diào)用被冒。當(dāng)系統(tǒng)調(diào)用Object.wait()、Thread.join()轮蜕、LockSupport.park()方法后昨悼,線程狀態(tài)轉(zhuǎn)換為WAITTING(等待狀態(tài))。而同樣的跃洛,如果調(diào)用的是Object.wait(long)率触、Thread.sleep(long)、Thread.join(long)汇竭、LockSupport.parkNanos()葱蝗、LockSupport.parkUntil()方法時(shí),會(huì)進(jìn)入TIMED_WAITING(超時(shí)等待)狀態(tài)细燎。當(dāng)線程進(jìn)入WAITING(等待狀態(tài))后需要系統(tǒng)調(diào)用Object.notify()两曼、Object.notifyAll()、LockSupport.unPark(Thread)方法才能重新喚醒線程玻驻。當(dāng)線程進(jìn)入TIMED_WAITING(超時(shí)等待)狀態(tài)后當(dāng)?shù)却龝r(shí)間超過long值后悼凑,線程會(huì)自動(dòng)喚醒,或者調(diào)用Object.notify()璧瞬、Object.notifyAll()户辫、LockSupport.unPark(Thread)后也會(huì)喚醒線程。當(dāng)線程在READY或RUNNING狀態(tài)中嗤锉,等待進(jìn)入synchronized方法或代碼塊的時(shí)候渔欢,即沒有獲取到對象鎖的時(shí)候,就將進(jìn)入阻塞狀態(tài)档冬,直到該線程獲取到對象鎖之后膘茎,重新進(jìn)入READY狀態(tài)。當(dāng)線程運(yùn)行結(jié)束后酷誓,進(jìn)入TERMINATED(線程終止)狀態(tài)披坏。
將上述過程總結(jié)之后,可以用下圖表示:
這里需要注意幾點(diǎn):
- 處于WAITING和TIMED_WAITING的線程也可能持有對象鎖盐数,比如調(diào)用Thread.sleep()方法進(jìn)入等待狀態(tài)的線程就有可能持有對象鎖棒拂。
- 當(dāng)線程遇到I/O的時(shí)候,還是處于RUNNABLE狀態(tài)
線程常用方法
sleep()方法
sleep()是Thread類的靜態(tài)方法,源碼如下:
public static native void sleep(long millis) throws InterruptedException;
它是native修飾的靜態(tài)方法帚屉,用于讓當(dāng)前線程按照指定的long值時(shí)間進(jìn)行休眠谜诫,其休眠時(shí)間的精度取決于處理器的計(jì)時(shí)器和調(diào)度器,在休眠指定時(shí)間后攻旦,線程會(huì)恢復(fù)執(zhí)行喻旷。需要注意的是sleep()方法會(huì)交出CPU,但是不會(huì)釋放對象鎖牢屋。從上面的線程狀態(tài)轉(zhuǎn)換圖中且预,可以看到sleep方法會(huì)使線程進(jìn)入TIMED_WAITTING狀態(tài)。Thread.sleep(long)方法經(jīng)常被拿來與Object.wait()方法進(jìn)行比較烙无,兩者的主要區(qū)別如下:
- sleep()方法為Thread類的靜態(tài)方法锋谐。而wait()方法為Object類的實(shí)例方法。
- wait()方法必須在synchronized修飾的同步塊或同步方法中調(diào)用截酷,否則會(huì)報(bào)InterruptedException異常涮拗,換言之,wait()方法調(diào)用必須持有對象鎖迂苛。而sleep方法則沒有這個(gè)限制,可以在任何地方使用三热。
- wait()方法會(huì)釋放CPU資源,并釋放對象鎖灾部,等待下次重新獲取資源康铭。而sleep()方法,只會(huì)釋放CPU資源赌髓,但是不會(huì)釋放對象鎖从藤。
- 調(diào)用sleep()方法的線程超過等待時(shí)間后,獲取到CPU時(shí)間片資源則會(huì)立即執(zhí)行锁蠕。而調(diào)用了wait()方法的線程必須等待Object.notify(),Object.notifyAll()之后夷野,才會(huì)嘗試重新獲取CPU資源,并執(zhí)行荣倾。
yield()方法
yield()也是Thread類的靜態(tài)方法悯搔,源碼如下:
public static native void yield();
當(dāng)執(zhí)行該方法時(shí),線程會(huì)讓出CPU舌仍,進(jìn)入就緒狀態(tài)妒貌。但需要注意的是,讓出CPU并不代表該線程不執(zhí)行了铸豁,當(dāng)前線程仍然會(huì)參與到下一次CPU時(shí)間片的競爭中灌曙,如果該線程在下一次競爭時(shí),仍然獲取到了CPU节芥,那么該線程會(huì)繼續(xù)執(zhí)行在刺。另外逆害,讓出的CPU時(shí)間片只允許與它相同優(yōu)先級(jí)的線程去競爭。下面說一下線程優(yōu)先級(jí)是個(gè)什么東西◎纪眨現(xiàn)代操作系統(tǒng)中基本采用時(shí)分的形式調(diào)度運(yùn)行的線程魄幕,操作系統(tǒng)會(huì)分出一個(gè)個(gè)時(shí)間片,線程會(huì)分配到若干時(shí)間片颖杏,當(dāng)線程分配到的時(shí)間片用完纯陨,就會(huì)發(fā)生線程調(diào)度,該線程只能等待下一次分配输玷。線程分配到的處理器時(shí)間多少也就決定了線程使用處理器資源的多少队丝,而線程優(yōu)先級(jí)就是決定線程分配時(shí)間多少的線程屬性。在Java中欲鹏,線程通過Thread中的一個(gè)int型私有成員變量來Priority(private int priority;
)來控制線程優(yōu)先級(jí),優(yōu)先級(jí)的范圍從1~10臭墨,可以在構(gòu)建線程的時(shí)候通過調(diào)用setPriority(priority)來設(shè)置赔嚎,默認(rèn)優(yōu)先級(jí)為5,優(yōu)先級(jí)高的線程相較于優(yōu)先級(jí)低的線程先獲取到CPU的時(shí)間片胧弛。具體源碼如下:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
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);
}
}
但是有一點(diǎn)需要注意尤误,不同JVM以及不同操作系統(tǒng)上,對線程的規(guī)劃是存在差異的结缚,有的操作系統(tǒng)甚至?xí)雎跃€程優(yōu)先級(jí)的設(shè)定损晤。yield()方法與sleep()方法一樣會(huì)釋放CPU資源,但是不會(huì)釋放對象鎖(如果當(dāng)前線程持有對象鎖的話)红竭;它們之間不同的是sleep()釋放的CPU資源所有線程都可以競爭尤勋,但是yield()釋放的資源只有相同優(yōu)先級(jí)的線程才能競爭
join()方法
如果在一個(gè)線程實(shí)例A中調(diào)用了threadB.join()方法,那么當(dāng)前線程A會(huì)等待線程B終止后才會(huì)繼續(xù)執(zhí)行茵宪。其在Thread類中的源碼如下:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
public final void join() throws InterruptedException {
join(0);
}
可以看到Thread中除了提供join()方法外最冰,還提供了超時(shí)等待相關(guān)的join(long)和join(long,int)方法。如果線程B超過給定時(shí)間還未執(zhí)行完成稀火,那么線程A會(huì)在線程B執(zhí)行超時(shí)后繼續(xù)執(zhí)行暖哨。翻看源碼發(fā)現(xiàn)join()和join(long,int)最終都是調(diào)用join(long)方法,而join(long)方法中多次調(diào)用了isAlive()方法凰狞。
public final native boolean isAlive();
該方法為native修飾的本地方法篇裁,該方法用于判斷一個(gè)線程是否存活∩娜簦可以看出來當(dāng)前等待對象threadA會(huì)一直阻塞达布,直到被等待對象threadB結(jié)束后即isAlive()返回false的時(shí)候才會(huì)結(jié)束while循環(huán),當(dāng)threadB退出時(shí)會(huì)調(diào)用notifyAll()方法通知所有的等待線程
下面來寫一個(gè)例子斩熊,看下join的作用
public static void main(String[] args) {
Thread preThread = Thread.currentThread();
for(int i= 0;i<10;i++){
Thread joinDemo = new JoinDemo(i,preThread);
joinDemo.start();
preThread = joinDemo;
}
}
static class JoinDemo extends Thread{
private int i;
private Thread preThread;
public JoinDemo(int i,Thread preThread){
this.i = i;
this.preThread = preThread;
}
@Override
public void run(){
try {
preThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+": "+ i);
}
}
這段代碼往枣,去除掉run()方法中的join方法調(diào)用的時(shí)候,答應(yīng)出的i順序是不確定的。但是加上join后分冈,即相當(dāng)于每個(gè)線程的執(zhí)行需要等待上一個(gè)線程執(zhí)行結(jié)束圾另,那么i就會(huì)按照自然順序打印到控制臺(tái),得到結(jié)果如下:
加join()方法前: 加join()方法后:
Thread-1: 1 Thread-0: 0
Thread-0: 0 Thread-1: 1
Thread-2: 2 Thread-2: 2
Thread-3: 3 Thread-3: 3
Thread-4: 4 Thread-4: 4
Thread-5: 5 Thread-5: 5
Thread-6: 6 Thread-6: 6
Thread-7: 7 Thread-7: 7
Thread-8: 8 Thread-8: 8
Thread-9: 9 Thread-9: 9
interrupt()方法
interrupt()方法為中斷線程方法雕沉,調(diào)用該方法并不是里面中斷線程集乔,而是將線程中的中斷標(biāo)志位設(shè)置為true;中斷好比其他線程對該線程打了一個(gè)招呼,其他線程可以調(diào)用該線程的interrupt()方法對其進(jìn)行中斷操作,而中斷的結(jié)果線程是死亡、還是等待新的任務(wù)或是繼續(xù)運(yùn)行至下一步坡椒,就取決于這個(gè)程序本身扰路。該線程可以調(diào)用isInterrupted()來感知其他線程對其自身的中斷操作,從而做出響應(yīng)倔叼。也可以調(diào)用Interrupted()方法來獲取中斷標(biāo)志位狀態(tài)汗唱,但是該方法獲取到標(biāo)志位狀態(tài)后,會(huì)將標(biāo)志位重新設(shè)置為false丈攒。需要注意的是哩罪,當(dāng)拋出InterruptedException時(shí)候,會(huì)清除中斷標(biāo)志位巡验,也就是說在調(diào)用isInterrupted會(huì)返回false际插。
方法名 | 詳細(xì)解釋 | 備注 |
---|---|---|
interrupt() | 中斷該線程對象 | 如果該線程被調(diào)用Object.wait()/wait(long) 或調(diào)用了Thread.sleep(long)/join()/join(long)方法時(shí), 會(huì)拋出InterruptedException異常,并清除中斷標(biāo)志位 |
isInterrupted() | 檢測該線程是否被中斷 | 中斷標(biāo)志位不會(huì)被清除显设,類似于get()方法 |
interrupted() | 檢測該線程是否被中斷 | 中斷標(biāo)志位會(huì)被清除框弛,類似于get()+set()方法 |
下面我們結(jié)合具體的例子來看下:
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
//sleepThread睡眠1000ms
final Thread sleepThread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.run();
}
};
//busyThread一直執(zhí)行死循環(huán)
Thread busyThread = new Thread() {
@Override
public void run() {
while (true) ;
}
};
sleepThread.start();
busyThread.start();
sleepThread.interrupt();
busyThread.interrupt();
while (sleepThread.isInterrupted()) ;
System.out.println("sleepThread isInterrupted: " + sleepThread.isInterrupted());
System.out.println("busyThread isInterrupted: " + busyThread.isInterrupted());
}
}
最終輸出結(jié)果如下:
sleepThread isInterrupted: false
busyThread isInterrupted: true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.xcy.javaConcurrent.InterruptDemo$1.run(InterruptDemo.java:14)
以上demo中開啟了兩個(gè)線程分別為sleepThread和BusyThread。sleepThread調(diào)用sleep方法睡眠1秒捕捂,busyThread則進(jìn)行死循環(huán)瑟枫。當(dāng)分別對兩個(gè)線程進(jìn)行interrupt操作時(shí),可以看出sleepThread拋出InterruptedException绞蹦,并清除了標(biāo)志位力奋。而busyThread則沒有清除標(biāo)志位。這里我們關(guān)注一下while (sleepThread.isInterrupted()) ;
這行代碼幽七,Interrupt()方法也可以看做線程之間的一種簡單交互方式景殷,這行代碼說明main方法會(huì)一直監(jiān)控sleepThread的中斷標(biāo)志位狀態(tài),當(dāng)中斷標(biāo)志位被清零時(shí)才會(huì)繼續(xù)往下執(zhí)行澡屡。
守護(hù)線程 isDaemon()方法###
Thread中的isDaemon方法用于判斷該線程是否為守護(hù)線程猿挚,守護(hù)線程是運(yùn)行在后臺(tái)的一種特殊進(jìn)程,它獨(dú)立于控制終端驶鹉,并且周期性地執(zhí)行某種任務(wù)或著等待處理某些發(fā)生的事件绩蜻。也就是在程序運(yùn)行的時(shí)候在后臺(tái)提供一種通用服務(wù)的線程,在沒有用戶線程客服務(wù)時(shí)會(huì)自動(dòng)離開室埋。用戶線程完全結(jié)束后就意味著整個(gè)系統(tǒng)的業(yè)務(wù)任務(wù)全部結(jié)束了办绝,因此系統(tǒng)就沒有對象需要守護(hù)的了伊约,守護(hù)線程自然而然就會(huì)退。當(dāng)一個(gè)Java應(yīng)用孕蝉,只有守護(hù)線程的時(shí)候屡律,虛擬機(jī)就會(huì)自然退出。例如垃圾回收線程降淮,JIT線程就可以理解守護(hù)線程超埋。
其在Thread中的源碼如下:
public final boolean isDaemon() {
return daemon;
}
注:本文參考:http://www.reibang.com/p/f65ea68a4a7f ,該文章作者有一系列關(guān)于java并發(fā)包知識(shí)的講解佳鳖,值得學(xué)習(xí)