概述
在說線程之前先說下進程,進程和線程都是一個時間段的描述,是CPU工作時間段的描述。
進程,是并發執行的程序在執行過程中分配和管理資源的基本單位,是一個動態概念,竟爭計算機系統資源的基本單位。每一個進程都有一個自己的地址空間,即進程空間或(虛空間)。
線程,在網絡或多用戶環境下,一個服務器通常需要接收大量且不確定數量用戶的并發請求,為每一個請求都創建一個進程顯然是行不通的,——無論是從系統資源開銷方面或是響應用戶請求的效率方面來看。因此,操作系統中線程的概念便被引進了。線程,是進程的一部分,一個沒有線程的進程可以被看作是單線程的。線程有時又被稱為輕權進程或輕量級進程,也是 CPU 調度的一個基本單位。
創建方式
線程的創建有三種方式:繼承Thread,實現Runnable接口,利用Callable跟Future
繼承Thread
(1)定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。因此把run()方法稱為執行體。 (2)創建Thread子類的實例,即創建了線程對象。 (3)調用線程對象的start()方法來啟動該線程。
public class FirstMethod extends Thread {@Overridepublic void run() {super.run();}}FirstMethod firstMethod = new FirstMethod();firstMethod.start();
復制代碼
實現Runnable接口
- (1)定義runnable接口的實現類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
- (2)創建 Runnable實現類的實例,并依此實例作為Thread的target來創建Thread對象,該Thread對象才是真正的線程對象。
- (3)調用線程對象的start()方法來啟動該線程。
public class SecondMethod implements Runnable{@Overridepublic void run() {}}
SecondMethod secondMethod=new SecondMethod();
new Thread(secondMethod).start();
復制代碼
通過Callable跟FutureTask創建線程
1)創建Callable接口的實現類,并實現call()方法,該call()方法將作為線程執行體,并且有返回值。 (2)創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。 (3)使用FutureTask對象作為Thread對象的target創建并啟動新線程。 (4)調用FutureTask對象的get()方法來獲得子線程執行結束后的返回值
public class ThirdMethod implements Callable<String> {@Overridepublic 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();}
復制代碼
對比分析
實現Runnable和實現Callable接口的方式基本相同,不過是后者執行call()方法有返回值,后者線程執行體run()方法無返回值,因此可以把這兩種方式歸為一種這種方式與繼承Thread類的方法之間的差別如下:
1、接口創建線程可以實現資源共享,比如多個線程可以共享一個Runnable資源 2、但是編程稍微復雜,如果需要訪問當前線程,必須調用Thread.currentThread()方法。 3、接口創建線可以避免由于Java的單繼承特性而帶來的局限。
現在通過一個程序員改Bug的例子來描述一下,一共有15個bug,現在安排3個程序員去Debug:
通過Thread來實現
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();}
}
復制代碼
運行結果:
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
復制代碼
通過Runnable來實現
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();}
復制代碼
運行結果
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[];//線程名稱的字節數組private int priority;//線程優先級private boolean single_step; //線程是否單步private boolean daemon = false; //是否是守護線程private boolean stillborn = false; //JVM stateprivate Runnable target; //從構造方法傳過來的Runnableprivate ThreadGroup group; //線程組private ClassLoader contextClassLoader; //類加載器private static int threadInitNumber; //線程編號private volatile int threadStatus = 0; //初始狀態public final static int MIN_PRIORITY = 1; //最低優先級public final static int NORM_PRIORITY = 5; //默認優先級public final static int MAX_PRIORITY = 10; //最高優先級復制代碼
線程狀態
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;}
復制代碼
線程的狀態有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,可以整理成如下表格
線程狀態 | 解釋 |
---|---|
New | 還未調用 start() 方法 |
RUNNABLE | 調用了 start() ,此時線程已經準備好被執行,處于就緒隊列 |
BLOCKED | 線程阻塞于鎖或者調用了 sleep |
WAITING | 線程由于某種原因等待其他線程 |
TIME_WAITING | 與 WAITING 的區別是可以在特定時間后自動返回 |
TERMINATED | 執行完畢或者被其他線程殺死 |
構造方法
/*** Initializes a Thread.** @param g //線程組* @param target //構造方法傳過來的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();//判斷線程組參數是否為空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();//定義是否為守護線程this.priority = parent.getPriority();//設置優先級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);//設置優先級if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;//設置棧深度/* Set thread ID */tid = nextThreadID();//設置線程ID}
復制代碼
start方法
public synchronized void start() {if (threadStatus != 0)//判斷線程是否準備好group.add(this);//將啟動的線程線程組boolean started = false;try {start0();//本地方法,JVM調用target的run方法started = true;//更改啟動標志} 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 */}}}復制代碼
@Overridepublic void run() {if (target != null) {target.run();}}
復制代碼
synchronized 關鍵字說明start方法是同步的,并且是啟動這個線程進行執行,JVM將會調用這個線程的run方法,這樣產生的結果是,兩個線程執行著,其中一個是調用start()方法的線程執行,另一個線程是執行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);//調用本地方法}
復制代碼
線程休眠一段時間,讓其他線程有機會繼續執行,需要捕捉異常。
yield()方法
public static native void yield();
復制代碼
- yield是一個靜態的原生(native)方法
- yield告訴當前正在執行的線程把運行機會交給線程池中擁有相同優先級的線程。
- yield不能保證使得當前正在運行的線程迅速轉換到可運行的狀態 它僅能使一個線程從運行狀態轉到可運行狀態,而不是等待或阻塞狀態
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方法是等待該線程執行,直到超時或者終止,可以作為線程通信的一種方式,A線程調用B線程的join(阻塞),等待B完成后再往下執行。
yield跟join
- join方法用線程對象調用,如果在一個線程A中調用另一個線程B的join方法,線程A將會等待線程B執行完畢后再執行。
- yield可以直接用Thread類調用,yield讓出CPU執行權給同等級的線程,如果沒有相同級別的線程在等待CPU的執行權,則該線程繼續執行。
interrupt()方法
public void interrupt() {if (this != Thread.currentThread())checkAccess();//檢查權限
synchronized (blockerLock) {Interruptible b = blocker;if (b != null) {interrupt0(); b.interrupt(this);return;}}interrupt0();}
復制代碼
interrupt()方法是中斷當前的線程, 此外還有isInterrupt,以及interrupted方法
- interrupt():將線程置為中斷狀態
- isInterrupt():線程是否中斷
- interrupted():返回線程的上次的中斷狀態,并清除中斷狀態。 一般來說,阻塞函數:如sleep()、join()、wait()等在檢查到線程的中斷狀態的時候,會拋出InteruptedExeption, 同時會清除線程的中斷狀態。
線程間通信
前面說過,Java中的線程在底層是通過共享內存進行通信的,在應用層則是通過調用Object對象的wait()方法和notify()方法或notifyAll()方法來實現線程間的通信。 Object是所有類的超類,主要通過:notify()、notifyAll()、wait()、wait(long)和wait(long,int)這幾個方法來進行線程間通信。
1、wait()
public final void wait() throws InterruptedException,IllegalMonitorStateException
復制代碼
- 休眠當前線程,釋放鎖,直到接到通知或被中斷為止
- 在調用wait()之前,線程必須要獲得該對象的對象級別鎖
2、notify()
public final native void notify() throws IllegalMonitorStateException
復制代碼
- 通知那些調用了wait()方法的線程。
- 每次只能通知單個線程,單個線程等待,則通知當前線程,如果有多個,則隨機抽取一個來進行通知
- 必須等到當前線程釋放鎖后,wait所在的線程也才可以獲取該對象鎖,但不驚動其他同樣在等待被該對象notify的線程們。
- wait()等待的是被notify或notifyAll,而不是鎖。
3、notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException
復制代碼
- 使所有原來在該對象上wait的線程統統退出wait的狀態
- 所有被通知的線程都會去競爭對象鎖。
- 獲得鎖的線程,會繼續往下執行,釋放鎖后,wait中的線程繼續競爭對象鎖
wait()和sleep()的區別
- sleep()方法是線程類Thread的靜態方法,導致此線程暫停執行指定時間,將執行機會給其他線程,但是監控狀態依然保持,到時后會自動恢復(線程回到就緒(ready)狀態),因為調用sleep 不會釋放對象鎖。
- wait()是Object 類的方法,對此對象調用wait()方法導致本線程放棄對象鎖(線程暫停執行),進入等待此對象的等待鎖定池,只有針對此對象發出notify 方法(或notifyAll)后本線程才進入對象鎖定池準備獲得對象鎖進入就緒狀態。
總結
通過對線程源碼的簡單分析,可以看出線程也是有自己的生命周期的,但是由于源碼中有很多native方法,導致了很難追蹤源碼,所以只能大致理解一下線程的各種狀態以及通信過程,下面可以通過一副流程圖來總結一下:
參考資料
Java編程思想
wangchangchung.github.io
www.jianshu.com/p/5b9fdae43…