02.并发编程(2)Thread类源码分析
概述
在說線程之前先說下進(jìn)程,進(jìn)程和線程都是一個時間段的描述,是CPU工作時間段的描述。
進(jìn)程,是并發(fā)執(zhí)行的程序在執(zhí)行過程中分配和管理資源的基本單位,是一個動態(tài)概念,竟?fàn)幱嬎銠C(jī)系統(tǒng)資源的基本單位。每一個進(jìn)程都有一個自己的地址空間,即進(jìn)程空間或(虛空間)。
線程,在網(wǎng)絡(luò)或多用戶環(huán)境下,一個服務(wù)器通常需要接收大量且不確定數(shù)量用戶的并發(fā)請求,為每一個請求都創(chuàng)建一個進(jìn)程顯然是行不通的,——無論是從系統(tǒng)資源開銷方面或是響應(yīng)用戶請求的效率方面來看。因此,操作系統(tǒng)中線程的概念便被引進(jìn)了。線程,是進(jìn)程的一部分,一個沒有線程的進(jìn)程可以被看作是單線程的。線程有時又被稱為輕權(quán)進(jìn)程或輕量級進(jìn)程,也是 CPU 調(diào)度的一個基本單位。
創(chuàng)建方式
線程的創(chuàng)建有三種方式:繼承Thread,實現(xiàn)Runnable接口,利用Callable跟Future
繼承Thread
(1)定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務(wù)。因此把run()方法稱為執(zhí)行體。 (2)創(chuàng)建Thread子類的實例,即創(chuàng)建了線程對象。 (3)調(diào)用線程對象的start()方法來啟動該線程。
public class FirstMethod extends Thread {@Overridepublic void run() {super.run();}}FirstMethod firstMethod = new FirstMethod();firstMethod.start(); 復(fù)制代碼實現(xiàn)Runnable接口
- (1)定義runnable接口的實現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體。
- (2)創(chuàng)建 Runnable實現(xiàn)類的實例,并依此實例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象。
- (3)調(diào)用線程對象的start()方法來啟動該線程。
通過Callable跟FutureTask創(chuàng)建線程
1)創(chuàng)建Callable接口的實現(xiàn)類,并實現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,并且有返回值。 (2)創(chuàng)建Callable實現(xiàn)類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。 (3)使用FutureTask對象作為Thread對象的target創(chuàng)建并啟動新線程。 (4)調(diào)用FutureTask對象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值
public class ThirdMethod implements Callable<String> {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();} 復(fù)制代碼對比分析
實現(xiàn)Runnable和實現(xiàn)Callable接口的方式基本相同,不過是后者執(zhí)行call()方法有返回值,后者線程執(zhí)行體run()方法無返回值,因此可以把這兩種方式歸為一種這種方式與繼承Thread類的方法之間的差別如下:
1、接口創(chuàng)建線程可以實現(xiàn)資源共享,比如多個線程可以共享一個Runnable資源 2、但是編程稍微復(fù)雜,如果需要訪問當(dāng)前線程,必須調(diào)用Thread.currentThread()方法。 3、接口創(chuàng)建線可以避免由于Java的單繼承特性而帶來的局限。
現(xiàn)在通過一個程序員改Bug的例子來描述一下,一共有15個bug,現(xiàn)在安排3個程序員去Debug:
通過Thread來實現(xiàn)
public class BugThread extends Thread {private volatile int bugNumber = 5;@Overridepublic 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();} } 復(fù)制代碼運行結(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 復(fù)制代碼通過Runnable來實現(xiàn)
public class BugRunnable implements Runnable {private volatile int bugNumber = 15;@Overridepublic 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();} 復(fù)制代碼運行結(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 復(fù)制代碼源碼分析
成員變量
private volatile char name[];//線程名稱的字節(jié)數(shù)組private int priority;//線程優(yōu)先級private boolean single_step; //線程是否單步private boolean daemon = false; //是否是守護(hù)線程private boolean stillborn = false; //JVM stateprivate Runnable target; //從構(gòu)造方法傳過來的Runnableprivate ThreadGroup group; //線程組private ClassLoader contextClassLoader; //類加載器private static int threadInitNumber; //線程編號private volatile int threadStatus = 0; //初始狀態(tài)public final static int MIN_PRIORITY = 1; //最低優(yōu)先級public final static int NORM_PRIORITY = 5; //默認(rèn)優(yōu)先級public final static int MAX_PRIORITY = 10; //最高優(yōu)先級復(fù)制代碼線程狀態(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 threadTERMINATED;} 復(fù)制代碼線程的狀態(tài)有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,可以整理成如下表格
| New | 還未調(diào)用 start() 方法 |
| RUNNABLE | 調(diào)用了 start() ,此時線程已經(jīng)準(zhǔn)備好被執(zhí)行,處于就緒隊列 |
| BLOCKED | 線程阻塞于鎖或者調(diào)用了 sleep |
| WAITING | 線程由于某種原因等待其他線程 |
| TIME_WAITING | 與 WAITING 的區(qū)別是可以在特定時間后自動返回 |
| TERMINATED | 執(zhí)行完畢或者被其他線程殺死 |
構(gòu)造方法
Thread有很多構(gòu)造方法,但是通過觀察最終調(diào)用了如下方法: /*** Initializes a Thread.** @param g //線程組* @param target //構(gòu)造方法傳過來的Runnable* @param name //線程名稱* @param stackSize //給線程分配的棧的深度* @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();//判斷線程組參數(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;//初始化線程組this.daemon = parent.isDaemon();//定義是否為守護(hù)線程this.priority = parent.getPriority();//設(shè)置優(yōu)先級if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;//初始化targetsetPriority(priority);//設(shè)置優(yōu)先級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è)置線程ID} 復(fù)制代碼start方法
public synchronized void start() {if (threadStatus != 0)//判斷線程是否準(zhǔn)備好group.add(this);//將啟動的線程線程組boolean started = false;try {start0();//本地方法,JVM調(diào)用target的run方法started = true;//更改啟動標(biāo)志} finally {try {if (!started)group.threadStartFailed(this);//通知線程組啟動失敗}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}}復(fù)制代碼public void run() {if (target != null) {target.run();}} 復(fù)制代碼synchronized 關(guān)鍵字說明start方法是同步的,并且是啟動這個線程進(jìn)行執(zhí)行,JVM將會調(diào)用這個線程的run方法,這樣產(chǎn)生的結(jié)果是,兩個線程執(zhí)行著,其中一個是調(diào)用start()方法的線程執(zhí)行,另一個線程是執(zhí)行run方法的線程。
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)用本地方法} 復(fù)制代碼線程休眠一段時間,讓其他線程有機(jī)會繼續(xù)執(zhí)行,需要捕捉異常。
yield()方法
public static native void yield(); 復(fù)制代碼- yield是一個靜態(tài)的原生(native)方法
- yield告訴當(dāng)前正在執(zhí)行的線程把運行機(jī)會交給線程池中擁有相同優(yōu)先級的線程。
- yield不能保證使得當(dāng)前正在運行的線程迅速轉(zhuǎn)換到可運行的狀態(tài) 它僅能使一個線程從運行狀態(tài)轉(zhuǎ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;}}} 復(fù)制代碼join方法是等待該線程執(zhí)行,直到超時或者終止,可以作為線程通信的一種方式,A線程調(diào)用B線程的join(阻塞),等待B完成后再往下執(zhí)行。
yield跟join
- join方法用線程對象調(diào)用,如果在一個線程A中調(diào)用另一個線程B的join方法,線程A將會等待線程B執(zhí)行完畢后再執(zhí)行。
- yield可以直接用Thread類調(diào)用,yield讓出CPU執(zhí)行權(quán)給同等級的線程,如果沒有相同級別的線程在等待CPU的執(zhí)行權(quá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();} 復(fù)制代碼interrupt()方法是中斷當(dāng)前的線程, 此外還有isInterrupt,以及interrupted方法
- interrupt():將線程置為中斷狀態(tài)
- isInterrupt():線程是否中斷
- interrupted():返回線程的上次的中斷狀態(tài),并清除中斷狀態(tài)。 一般來說,阻塞函數(shù):如sleep()、join()、wait()等在檢查到線程的中斷狀態(tài)的時候,會拋出InteruptedExeption, 同時會清除線程的中斷狀態(tài)。
線程間通信
前面說過,Java中的線程在底層是通過共享內(nèi)存進(jìn)行通信的,在應(yīng)用層則是通過調(diào)用Object對象的wait()方法和notify()方法或notifyAll()方法來實現(xiàn)線程間的通信。 Object是所有類的超類,主要通過:notify()、notifyAll()、wait()、wait(long)和wait(long,int)這幾個方法來進(jìn)行線程間通信。
1、wait()
public final void wait() throws InterruptedException,IllegalMonitorStateException 復(fù)制代碼- 休眠當(dāng)前線程,釋放鎖,直到接到通知或被中斷為止
- 在調(diào)用wait()之前,線程必須要獲得該對象的對象級別鎖
2、notify()
public final native void notify() throws IllegalMonitorStateException 復(fù)制代碼- 通知那些調(diào)用了wait()方法的線程。
- 每次只能通知單個線程,單個線程等待,則通知當(dāng)前線程,如果有多個,則隨機(jī)抽取一個來進(jìn)行通知
- 必須等到當(dāng)前線程釋放鎖后,wait所在的線程也才可以獲取該對象鎖,但不驚動其他同樣在等待被該對象notify的線程們。
- wait()等待的是被notify或notifyAll,而不是鎖。
3、notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException 復(fù)制代碼- 使所有原來在該對象上wait的線程統(tǒng)統(tǒng)退出wait的狀態(tài)
- 所有被通知的線程都會去競爭對象鎖。
- 獲得鎖的線程,會繼續(xù)往下執(zhí)行,釋放鎖后,wait中的線程繼續(xù)競爭對象鎖
wait()和sleep()的區(qū)別
- sleep()方法是線程類Thread的靜態(tài)方法,導(dǎo)致此線程暫停執(zhí)行指定時間,將執(zhí)行機(jī)會給其他線程,但是監(jiān)控狀態(tài)依然保持,到時后會自動恢復(fù)(線程回到就緒(ready)狀態(tài)),因為調(diào)用sleep 不會釋放對象鎖。
- wait()是Object 類的方法,對此對象調(diào)用wait()方法導(dǎo)致本線程放棄對象鎖(線程暫停執(zhí)行),進(jìn)入等待此對象的等待鎖定池,只有針對此對象發(fā)出notify 方法(或notifyAll)后本線程才進(jìn)入對象鎖定池準(zhǔn)備獲得對象鎖進(jìn)入就緒狀態(tài)。
總結(jié)
通過對線程源碼的簡單分析,可以看出線程也是有自己的生命周期的,但是由于源碼中有很多native方法,導(dǎo)致了很難追蹤源碼,所以只能大致理解一下線程的各種狀態(tài)以及通信過程,下面可以通過一副流程圖來總結(jié)一下:
參考資料
Java編程思想
wangchangchung.github.io
www.jianshu.com/p/5b9fdae43…
總結(jié)
以上是生活随笔為你收集整理的02.并发编程(2)Thread类源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: creator qt 字体太小_QtCr
- 下一篇: “约见“面试官系列之各系列目录汇总(建议