概述
在說(shuō)線(xiàn)程之前先說(shuō)下進(jìn)程,進(jìn)程和線(xiàn)程都是一個(gè)時(shí)間段的描述,是CPU工作時(shí)間段的描述。
進(jìn)程,是并發(fā)執(zhí)行的程序在執(zhí)行過(guò)程中分配和管理資源的基本單位,是一個(gè)動(dòng)態(tài)概念,竟?fàn)幱?jì)算機(jī)系統(tǒng)資源的基本單位。每一個(gè)進(jìn)程都有一個(gè)自己的地址空間,即進(jìn)程空間或(虛空間)。
線(xiàn)程,在網(wǎng)絡(luò)或多用戶(hù)環(huán)境下,一個(gè)服務(wù)器通常需要接收大量且不確定數(shù)量用戶(hù)的并發(fā)請(qǐng)求,為每一個(gè)請(qǐng)求都創(chuàng)建一個(gè)進(jìn)程顯然是行不通的,——無(wú)論是從系統(tǒng)資源開(kāi)銷(xiāo)方面或是響應(yīng)用戶(hù)請(qǐng)求的效率方面來(lái)看。因此,操作系統(tǒng)中線(xiàn)程的概念便被引進(jìn)了。線(xiàn)程,是進(jìn)程的一部分,一個(gè)沒(méi)有線(xiàn)程的進(jìn)程可以被看作是單線(xiàn)程的。線(xiàn)程有時(shí)又被稱(chēng)為輕權(quán)進(jìn)程或輕量級(jí)進(jìn)程,也是 CPU 調(diào)度的一個(gè)基本單位。
創(chuàng)建方式
線(xiàn)程的創(chuàng)建有三種方式:繼承Thread,實(shí)現(xiàn)Runnable接口,利用Callable跟Future
繼承Thread
(1)定義Thread類(lèi)的子類(lèi),并重寫(xiě)該類(lèi)的run方法,該run方法的方法體就代表了線(xiàn)程要完成的任務(wù)。因此把run()方法稱(chēng)為執(zhí)行體。
(2)創(chuàng)建Thread子類(lèi)的實(shí)例,即創(chuàng)建了線(xiàn)程對(duì)象。
(3)調(diào)用線(xiàn)程對(duì)象的start()方法來(lái)啟動(dòng)該線(xiàn)程。
public class FirstMethod extends Thread {
@Override
public void run() {
super.run();
}
}
FirstMethod firstMethod = new FirstMethod();
firstMethod.start();
實(shí)現(xiàn)Runnable接口
- (1)定義runnable接口的實(shí)現(xiàn)類(lèi),并重寫(xiě)該接口的run()方法,該run()方法的方法體同樣是該線(xiàn)程的線(xiàn)程執(zhí)行體。
- (2)創(chuàng)建 Runnable實(shí)現(xiàn)類(lèi)的實(shí)例,并依此實(shí)例作為T(mén)hread的target來(lái)創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線(xiàn)程對(duì)象。
- (3)調(diào)用線(xiàn)程對(duì)象的start()方法來(lái)啟動(dòng)該線(xiàn)程。
public class SecondMethod implements Runnable{
@Override
public void run() {
}
}
SecondMethod secondMethod=new SecondMethod();
new Thread(secondMethod).start();
通過(guò)Callable跟FutureTask創(chuàng)建線(xiàn)程
1)創(chuàng)建Callable接口的實(shí)現(xiàn)類(lèi),并實(shí)現(xiàn)call()方法,該call()方法將作為線(xiàn)程執(zhí)行體,并且有返回值。
(2)創(chuàng)建Callable實(shí)現(xiàn)類(lèi)的實(shí)例,使用FutureTask類(lèi)來(lái)包裝Callable對(duì)象,該FutureTask對(duì)象封裝了該Callable對(duì)象的call()方法的返回值。
(3)使用FutureTask對(duì)象作為T(mén)hread對(duì)象的target創(chuàng)建并啟動(dòng)新線(xiàn)程。
(4)調(diào)用FutureTask對(duì)象的get()方法來(lái)獲得子線(xiàn)程執(zhí)行結(jié)束后的返回值
public class ThirdMethod implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName();
}
}
ThirdMethod thirdMethod=new ThirdMethod();
FutureTask<String> futureTask=new FutureTask<String>(thirdMethod);
try {
String threadName = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
對(duì)比分析
實(shí)現(xiàn)Runnable和實(shí)現(xiàn)Callable接口的方式基本相同,不過(guò)是后者執(zhí)行call()方法有返回值,后者線(xiàn)程執(zhí)行體run()方法無(wú)返回值,因此可以把這兩種方式歸為一種這種方式與繼承Thread類(lèi)的方法之間的差別如下:
1、接口創(chuàng)建線(xiàn)程可以實(shí)現(xiàn)資源共享,比如多個(gè)線(xiàn)程可以共享一個(gè)Runnable資源
2、但是編程稍微復(fù)雜,如果需要訪(fǎng)問(wèn)當(dāng)前線(xiàn)程,必須調(diào)用Thread.currentThread()方法。
3、接口創(chuàng)建線(xiàn)可以避免由于Java的單繼承特性而帶來(lái)的局限。
現(xiàn)在通過(guò)一個(gè)程序員改Bug的例子來(lái)描述一下,一共有15個(gè)bug,現(xiàn)在安排3個(gè)程序員去Debug:
通過(guò)Thread來(lái)實(shí)現(xiàn)
public class BugThread extends Thread {
private volatile int bugNumber = 5;
@Override
public void run() {
for (int i = 0; i < bugNumber; i++) {
System.out.println("bugNumber--->" + bugNumber--);
}
}
}
public class Main {
public static void main(String[] args) {
new BugThread().start();
new BugThread().start();
new BugThread().start();
}
}
運(yùn)行結(jié)果:
Thread-0-----5
Thread-1-----5
Thread-2-----5
Thread-0-----4
Thread-2-----4
Thread-1-----4
Thread-2-----3
Thread-0-----3
Thread-2-----2
Thread-1-----3
Thread-2-----1
Thread-0-----2
Thread-0-----1
Thread-1-----2
Thread-1-----1
通過(guò)Runnable來(lái)實(shí)現(xiàn)
public class BugRunnable implements Runnable {
private volatile int bugNumber = 15;
@Override
public void run() {
while (bugNumber > 0)
System.out.println(Thread.currentThread().getName() + "-----" + bugNumber--);
}
}
public static void main(String[] args) {
BugRunnable bugRunnable = new BugRunnable();
new Thread(bugRunnable).start();
new Thread(bugRunnable).start();
new Thread(bugRunnable).start();
}
運(yùn)行結(jié)果
Thread-0-----15
Thread-0-----14
Thread-0-----13
Thread-0-----12
Thread-1-----11
Thread-0-----10
Thread-1-----9
Thread-0-----8
Thread-1-----7
Thread-0-----6
Thread-1-----5
Thread-0-----4
Thread-1-----3
Thread-0-----2
Thread-1-----1
源碼分析
成員變量
private volatile char name[];//線(xiàn)程名稱(chēng)的字節(jié)數(shù)組
private int priority;//線(xiàn)程優(yōu)先級(jí)
private boolean single_step; //線(xiàn)程是否單步
private boolean daemon = false; //是否是守護(hù)線(xiàn)程
private boolean stillborn = false; //JVM state
private Runnable target; //從構(gòu)造方法傳過(guò)來(lái)的Runnable
private ThreadGroup group; //線(xiàn)程組
private ClassLoader contextClassLoader; //類(lèi)加載器
private static int threadInitNumber; //線(xiàn)程編號(hào)
private volatile int threadStatus = 0; //初始狀態(tài)
public final static int MIN_PRIORITY = 1; //最低優(yōu)先級(jí)
public final static int NORM_PRIORITY = 5; //默認(rèn)優(yōu)先級(jí)
public final static int MAX_PRIORITY = 10; //最高優(yōu)先級(jí)
線(xiàn)程狀態(tài)
public enum State {
//Thread state for a thread which has not yet started.
NEW,
//Thread state for a runnable thread.
RUNNABLE,
//Thread state for a thread blocked waiting for a monitor lock.
BLOCKED,
// Thread state for a waiting thread.
WAITING,
//Thread state for a waiting thread with a specified waiting time.
TIMED_WAITING,
//Thread state for a terminated thread
TERMINATED;
}
線(xiàn)程狀態(tài) | 解釋 |
---|---|
New | 還未調(diào)用 start() 方法 |
RUNNABLE | 調(diào)用了 start() ,此時(shí)線(xiàn)程已經(jīng)準(zhǔn)備好被執(zhí)行,處于就緒隊(duì)列 |
BLOCKED | 線(xiàn)程阻塞于鎖或者調(diào)用了 sleep |
WAITING | 線(xiàn)程由于某種原因等待其他線(xiàn)程 |
TIME_WAITING | 與 WAITING 的區(qū)別是可以在特定時(shí)間后自動(dòng)返回 |
TERMINATED | 執(zhí)行完畢或者被其他線(xiàn)程殺死 |
線(xiàn)程的狀態(tài)有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,可以整理成如下表格
線(xiàn)程狀態(tài) | 解釋 |
---|---|
New | 還未調(diào)用 start() 方法 |
RUNNABLE | 調(diào)用了 start() ,此時(shí)線(xiàn)程已經(jīng)準(zhǔn)備好被執(zhí)行,處于就緒隊(duì)列 |
BLOCKED | 線(xiàn)程阻塞于鎖或者調(diào)用了 sleep |
WAITING | 線(xiàn)程由于某種原因等待其他線(xiàn)程 |
TIME_WAITING | 與 WAITING 的區(qū)別是可以在特定時(shí)間后自動(dòng)返回 |
TERMINATED | 執(zhí)行完畢或者被其他線(xiàn)程殺死 |
構(gòu)造方法
Thread有很多構(gòu)造方法,但是通過(guò)觀(guān)察最終調(diào)用了如下方法:
/**
* Initializes a Thread.
*
* @param g //線(xiàn)程組
* @param target //構(gòu)造方法傳過(guò)來(lái)的Runnable
* @param name //線(xiàn)程名稱(chēng)
* @param stackSize //給線(xiàn)程分配的棧的深度
* @param acc //上下文加載器
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
//判斷線(xiàn)程組參數(shù)是否為空
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;//初始化線(xiàn)程組
this.daemon = parent.isDaemon();//定義是否為守護(hù)線(xiàn)程
this.priority = parent.getPriority();//設(shè)置優(yōu)先級(jí)
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;//初始化target
setPriority(priority);//設(shè)置優(yōu)先級(jí)
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;//設(shè)置棧深度
/* Set thread ID */
tid = nextThreadID();//設(shè)置線(xiàn)程ID
}
start方法
public synchronized void start() {
if (threadStatus != 0)//判斷線(xiàn)程是否準(zhǔn)備好
group.add(this);//將啟動(dòng)的線(xiàn)程線(xiàn)程組
boolean started = false;
try {
start0();//本地方法,JVM調(diào)用target的run方法
started = true;//更改啟動(dòng)標(biāo)志
} finally {
try {
if (!started)
group.threadStartFailed(this);//通知線(xiàn)程組啟動(dòng)失敗
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
synchronized 關(guān)鍵字說(shuō)明start方法是同步的,并且是啟動(dòng)這個(gè)線(xiàn)程進(jìn)行執(zhí)行,JVM將會(huì)調(diào)用這個(gè)線(xiàn)程的run方法,這樣產(chǎn)生的結(jié)果是,兩個(gè)線(xiàn)程執(zhí)行著,其中一個(gè)是調(diào)用start()方法的線(xiàn)程執(zhí)行,另一個(gè)線(xiàn)程是執(zhí)行run方法的線(xiàn)程。
sleep()方法
public static void sleep(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++;
}
sleep(millis);//調(diào)用本地方法
}
線(xiàn)程休眠一段時(shí)間,讓其他線(xiàn)程有機(jī)會(huì)繼續(xù)執(zhí)行,需要捕捉異常。
yield()方法
public static native void yield();
- yield是一個(gè)靜態(tài)的原生(native)方法
- yield告訴當(dāng)前正在執(zhí)行的線(xiàn)程把運(yùn)行機(jī)會(huì)交給線(xiàn)程池中擁有相同優(yōu)先級(jí)的線(xiàn)程。
- yield不能保證使得當(dāng)前正在運(yùn)行的線(xiàn)程迅速轉(zhuǎn)換到可運(yùn)行的狀態(tài)
它僅能使一個(gè)線(xiàn)程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),而不是等待或阻塞狀態(tài)
join()方法
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;
}
}
}
join方法是等待該線(xiàn)程執(zhí)行,直到超時(shí)或者終止,可以作為線(xiàn)程通信的一種方式,A線(xiàn)程調(diào)用B線(xiàn)程的join(阻塞),等待B完成后再往下執(zhí)行。
yield跟join
- join方法用線(xiàn)程對(duì)象調(diào)用,如果在一個(gè)線(xiàn)程A中調(diào)用另一個(gè)線(xiàn)程B的join方法,線(xiàn)程A將會(huì)等待線(xiàn)程B執(zhí)行完畢后再執(zhí)行。
- yield可以直接用Thread類(lèi)調(diào)用,yield讓出CPU執(zhí)行權(quán)給同等級(jí)的線(xiàn)程,如果沒(méi)有相同級(jí)別的線(xiàn)程在等待CPU的執(zhí)行權(quán),則該線(xiàn)程繼續(xù)執(zhí)行。
interrupt()方法
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();//檢查權(quán)限
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
interrupt()方法是中斷當(dāng)前的線(xiàn)程, 此外還有isInterrupt,以及interrupted方法
- interrupt():將線(xiàn)程置為中斷狀態(tài)
- isInterrupt():線(xiàn)程是否中斷
- interrupted():返回線(xiàn)程的上次的中斷狀態(tài),并清除中斷狀態(tài)。
一般來(lái)說(shuō),阻塞函數(shù):如sleep()、join()、wait()等在檢查到線(xiàn)程的中斷狀態(tài)的時(shí)候,會(huì)拋出InteruptedExeption, 同時(shí)會(huì)清除線(xiàn)程的中斷狀態(tài)。
線(xiàn)程間通信
前面說(shuō)過(guò),Java中的線(xiàn)程在底層是通過(guò)共享內(nèi)存進(jìn)行通信的,在應(yīng)用層則是通過(guò)調(diào)用Object對(duì)象的wait()方法和notify()方法或notifyAll()方法來(lái)實(shí)現(xiàn)線(xiàn)程間的通信。
Object是所有類(lèi)的超類(lèi),主要通過(guò):notify()、notifyAll()、wait()、wait(long)和wait(long,int)這幾個(gè)方法來(lái)進(jìn)行線(xiàn)程間通信。
1、wait()
public final void wait() throws InterruptedException,IllegalMonitorStateException
- 休眠當(dāng)前線(xiàn)程,釋放鎖,直到接到通知或被中斷為止
- 在調(diào)用wait()之前,線(xiàn)程必須要獲得該對(duì)象的對(duì)象級(jí)別鎖
2、notify()
public final native void notify() throws IllegalMonitorStateException
- 通知那些調(diào)用了wait()方法的線(xiàn)程。
- 每次只能通知單個(gè)線(xiàn)程,單個(gè)線(xiàn)程等待,則通知當(dāng)前線(xiàn)程,如果有多個(gè),則隨機(jī)抽取一個(gè)來(lái)進(jìn)行通知
- 必須等到當(dāng)前線(xiàn)程釋放鎖后,wait所在的線(xiàn)程也才可以獲取該對(duì)象鎖,但不驚動(dòng)其他同樣在等待被該對(duì)象notify的線(xiàn)程們。
- wait()等待的是被notify或notifyAll,而不是鎖。
3、notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException
- 使所有原來(lái)在該對(duì)象上wait的線(xiàn)程統(tǒng)統(tǒng)退出wait的狀態(tài)
- 所有被通知的線(xiàn)程都會(huì)去競(jìng)爭(zhēng)對(duì)象鎖。
- 獲得鎖的線(xiàn)程,會(huì)繼續(xù)往下執(zhí)行,釋放鎖后,wait中的線(xiàn)程繼續(xù)競(jìng)爭(zhēng)對(duì)象鎖
wait()和sleep()的區(qū)別
- sleep()方法是線(xiàn)程類(lèi)Thread的靜態(tài)方法,導(dǎo)致此線(xiàn)程暫停執(zhí)行指定時(shí)間,將執(zhí)行機(jī)會(huì)給其他線(xiàn)程,但是監(jiān)控狀態(tài)依然保持,到時(shí)后會(huì)自動(dòng)恢復(fù)(線(xiàn)程回到就緒(ready)狀態(tài)),因?yàn)檎{(diào)用sleep 不會(huì)釋放對(duì)象鎖。
- wait()是Object 類(lèi)的方法,對(duì)此對(duì)象調(diào)用wait()方法導(dǎo)致本線(xiàn)程放棄對(duì)象鎖(線(xiàn)程暫停執(zhí)行),進(jìn)入等待此對(duì)象的等待鎖定池,只有針對(duì)此對(duì)象發(fā)出notify 方法(或notifyAll)后本線(xiàn)程才進(jìn)入對(duì)象鎖定池準(zhǔn)備獲得對(duì)象鎖進(jìn)入就緒狀態(tài)。
總結(jié)
通過(guò)對(duì)線(xiàn)程源碼的簡(jiǎn)單分析,可以看出線(xiàn)程也是有自己的生命周期的,但是由于源碼中有很多native方法,導(dǎo)致了很難追蹤源碼,所以只能大致理解一下線(xiàn)程的各種狀態(tài)以及通信過(guò)程,下面可以通過(guò)一副流程圖來(lái)總結(jié)一下:
參考資料
Java編程思想
https://wangchangchung.github.io
http://www.lxweimin.com/p/5b9fdae43335