程式與執行緒
- 程式是程式向作業系統申請資源(如記憶體空間和檔案控制程式碼)的基本單位.
- 執行緒是程式中可獨立執行的最小單位.
JAVA執行緒API
- 在Java中建立一個執行緒就是建立一個Thread類的例項。
- 每個執行緒都有其要執行的任務.執行緒任務的處理邏輯是在Thread類的run方法中實現或進行呼叫的,因此run方法相當於執行緒任務邏輯處理的入口方法,它由java虛擬機器在執行相應的執行緒時進行呼叫,而不是由應用程式碼進行呼叫。
- Thread類的start方法是用於啟動相關執行緒的,啟動一個執行緒的實質是請求java虛擬機器執行相應的執行緒,而這個執行緒具體什麼時候執行是由執行緒排程器決定的.因此,雖然start方法被執行了,但是並不意味著這個執行緒就開始執行了,它可能稍後執行,也可能永遠不執行.
Thread中常用的兩個構造器是:Thread()和Thread(Runnable target),這兩種建立執行緒的方式如下:
定義Thread類子類的方式建立執行緒
public class WelcomeApp { public static void main(String[] args) { // 建立執行緒 Thread welcomeThread = new WelcomeThread(); // 啟動執行緒 welcomeThread.start(); // 輸出“當前執行緒”的執行緒名稱 System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName()); } } // 定義Thread類的子類 class WelcomeThread extends Thread { // 在該方法中實現執行緒的任務處理邏輯 @Override public void run() { System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName()); } }
實現Runnable介面的方式建立執行緒
public class WelcomeApp1 { public static void main(String[] args) { // 建立執行緒 Thread welcomeThread = new Thread(new WelcomeTask()); // 啟動執行緒 welcomeThread.start(); // 輸出“當前執行緒”的執行緒名稱 System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName()); } } class WelcomeTask implements Runnable { // 在該方法中實現執行緒的任務處理邏輯 @Override public void run() { // 輸出“當前執行緒”的執行緒名稱 System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName()); } }
- 不管使用以上哪種方式執行,一旦執行緒的run方法執行結束,相應執行緒的執行也就結束了,執行結束的執行緒所佔的資源會如同其它java物件一樣被java虛擬機器垃圾回收。
- Thread的例項只能start一次,若多次呼叫一個例項的
start()
會丟擲IllegalThreadStateException
異常。 可以通過Thread.currentThread()獲取當前執行緒,進而可以對其進行屬性設定或獲取它的相關資訊,例如:
Thread.currentThread().setName("執行緒A"); Thread.currentThread().getName();
上述中,執行緒的run方法一般由java虛擬機器呼叫,但是,執行緒也是一個Thread類的一個例項其次run方法也是由public修飾符修飾,所以run方法也能被直接呼叫,但是一般不會這麼做,違揹我們床架執行緒的初衷。
public class WelcomeApp { public static void main(String[] args) { // 建立執行緒 Thread welcomeThread = new WelcomeThread(); // 啟動執行緒 welcomeThread.start(); // 輸出“當前執行緒”的執行緒名稱 System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName()); welcomeThread.run(); } } // 定義Thread類的子類 class WelcomeThread extends Thread { // 在該方法中實現執行緒的任務處理邏輯 @Override public void run() { System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName()); } } ==================結果================== 1.Welcome! I'm main. 2.Welcome! I'm Thread-0. 2.Welcome! I'm main.
執行緒的屬性
屬性 屬性型別及用途 只讀 重要注意事項 編號
ID用於標識不同的執行緒,不同的執行緒擁有不同的編號 是 某個編號的執行緒執行結束後,該編號可能被後續建立的執行緒使用。不同執行緒擁有的編號雖然不同,但是這種編號的唯一性只在Java虛擬機器的一次執行有效。也就是說重啟一個Java虛擬機器(如重啟Web伺服器)後,某些執行緒的編號可能與上次Java虛擬機器執行的某個執行緒的編號一樣,因此該屬性的值不適合用作某種唯一標識,特別是作為資料庫中的唯一標識(如主鍵) 名稱
Name用於區分不同的執行緒。預設值與執行緒的編號有關,預設值的格式為:Thread-執行緒編號,如Thread-0 否 Java並不禁止我們將不同的執行緒的名稱屬性設定為相同的值。儘管如此,設定執行緒的名稱屬性有助於程式碼除錯和問題定位 執行緒類別
Daemon值為true表示相應的執行緒為守護執行緒,否則表示相應的執行緒為使用者執行緒。該屬性的預設值與相應執行緒的父執行緒的該屬性的值相同,在正常停止java程式時,當有使用者執行緒還沒執行完虛擬機器不會立即停止,會等待其執行完畢,但是如果只有守護執行緒還沒執行完則不會阻止虛擬機器停止,這說明守護執行緒通常用於執行一些重要性不是很高的任務,例如監視其它執行緒的 否 該屬性必須在相應執行緒啟動之前設定,即對setDaemon方法的呼叫必須在對start方法的呼叫之前,否則setDaemon方法會丟擲IllegalThreadStateException異常。負責一些關鍵任務處理的執行緒不適宜設定為守護執行緒 優先順序
Priority該屬性本質上是給執行緒排程器的提示,用於表示應用程式希望哪個執行緒能夠優先得以執行。Java定義了1~10的10個優先順序。預設值一般為5。對於具體的一個執行緒而言,其優先順序的預設值與其父執行緒(建立該執行緒的執行緒)的優先順序值相等 否 一般使用預設優先順序即可。不恰當地設定該屬性值可能導致嚴重的問題(執行緒飢餓) 執行緒方法
方法 功能 備註 static Thread
currentThread()返回當前執行緒,即當前程式碼的執行執行緒(物件) 同一段程式碼對Thread.currentThread()的呼叫,其返回值可能對應著不同的執行緒(物件) void run() 用於實現執行緒的任務處理邏輯 該方法是由Java虛擬機器直接呼叫的,一般情況下應用程式不應該呼叫該方法 void start() 啟動相應執行緒 該方法的返回並不代表相應的執行緒已經被啟動。一個Thread例項的start方法只能夠被呼叫一次,多次呼叫會導致異常的丟擲 void join() 等待相應執行緒執行結束 若執行緒A呼叫執行緒B的join方法,那麼執行緒A的執行會被暫停,直到執行緒B執行結束 static void yield() 使當前執行緒主動放棄其對處理器的佔用,這可能導致當前執行緒被暫停 這個方法是不可靠的。該方法被呼叫時當前執行緒可能仍然繼續執行(視系統當前的執行狀況而定) static void sleep(long millis) 使當前執行緒休眠(暫停執行)指定的時間
Thread和Runnable的關係
Thread類實現Runnable介面
public class Thread implements Runnable{}
- Runnable是一個interface且其中只有一個方法,所以實現Runnable的類要重新run方法。
兩種方式執行任務邏輯的過程
實現Runnable介面,重寫run方法
class WelcomeTask implements Runnable { @Override public void run() { //任務邏輯 } }
new Thread(new WelcomeTask()).start();
WelcomeTask
例項在Thread中的傳遞過程,它最終會被賦值給Thread中的一個target成員變數private Runnable target;
具體過程如下:
1.有參建構函式,生成一個ThreadName,例如:Thread-0public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
2.進入第一個初始化函式,這裡面無需關注其它引數,因為還有其它構造方法會呼叫init(...)
private void init(ThreadGroup g, Runnable target, String name,long stackSize){ init(g, target, name, stackSize, null, true); }
3.第二層初始化函式,即將target賦值
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; ..... this.target = target; .... }
4.Thread例項呼叫start()方法
public synchronized void start() { //threadStatus != 0代表這個Thread例項已經被start()過了,所以會丟擲異常 if (threadStatus != 0) throw new IllegalThreadStateException(); ... try { //最終會呼叫本地方法start0()來啟動這個執行緒 start0(); ... } finally { ... } }
5.本地方法啟動執行緒,即將執行緒交給虛擬機器的執行緒排程器
private native void start0();
6.執行緒排程器執行執行緒任務邏輯
//啟動執行緒後,排程器會幫助我們執行執行執行緒run()方法,即任務邏輯 //如果target != null的時候會呼叫WelcomeTask中實現的任務邏輯,否則什麼都不會執行 public void run() { if (target != null) { target.run(); } }
繼承Thread類,重寫run方法
class WelcomeThread extends Thread { @Override public void run() { //任務邏輯 } }
new WelcomeThread().start(); ... //任務排程器直接呼叫WelcomeThread中實現的任務邏輯
兩種方式的區別
- 物件導向角度:一種是基於繼承實現
extends Thread
,一種是基於組合new Thread(new Runable())
,組合相對與繼承來說耦合性更低。 物件共享的角度:一個
CountingTask
例項可以被多個執行緒共享所以可能出現資源競爭問題。class CountingThread extends Thread { int count = 0; @Override public void run() { for (int i = 0; i < 100; i++) { i++; } } System.out.println("count:" + count); } class CountingTask implments Runnable { int count = 0; @Override public void run() { for (int i = 0; i < 100; i++) { i++; } } System.out.println("count:" + count); } public static void main(String[] args) { Thread thread; CountingTask task = new CountingTask(); for(int i = 0;i < 10;i++){ thread = new Thread(task); thread.start(); } for(int i = 0;i < 10;i++){ thread = new CountingThread(); thread.start(); } }
- 物件導向角度:一種是基於繼承實現
執行緒的生命週期
- New(新建立)
Thread thread = new Thread();
此時執行緒只是備new出來並沒有開始執行。 - Runnable(可執行)
thread.start();
1.ready:還沒有被分配資源 。
2.running:正在執行 。 阻塞
1.Blocked(阻塞) 沒有獲取被synchronized保護的程式碼的鎖 。
2.Waiting(等待)1.Object.wait(); - o.notify()/o.notifyAll() 2.Thread.join(); - join執行緒結束/被中斷 Blocked與Waiting的區別是Blocked是在等待釋放某個資源,Waiting是在等待達到某個條件 。
Time Waiting(計時等待) 設定了時間引數的一些阻塞 。
1.Thread.sleep(m);- 時間超時/join的程式結束/被中斷 。
2.Object.wait(m)
- 時間到/o.notify()/o.notifyAll()
3.Thread.join(m)
- 時間超時/join的程式結束/被中斷 。
- Terminated(終止)
1.run方法正常執行完畢 。
2.出現沒有捕獲的異常,意外終止。