1. 程式人生 > >多執行緒詳解——執行緒基本概念

多執行緒詳解——執行緒基本概念

0. 簡介

這個系列開始來講解 Java 多執行緒的知識,這節就先講解多執行緒的基本知識。

1. 程序與執行緒

1.1 什麼是程序?

程序就是在執行過程中的程式,就好像手機執行中的微信,QQ,這些就叫做程序。

1.2 什麼是執行緒?

執行緒就是程序的執行單元,就好像一個音樂軟體可以聽音樂,下載音樂,這些任務都是由執行緒來完成的。

1.3 程序與執行緒的關係

  • 一個程序可以擁有多個執行緒,一個執行緒必須要有一個父程序
  • 執行緒之間共享父程序的共享資源,相互之間協同完成程序所要完成的任務
  • 一個執行緒可以建立和撤銷另一個執行緒,同一個程序的多個執行緒之間可以併發執行

2. 如何建立執行緒

Java 中建立執行緒的方法有三種,以下來逐一詳細講解。

2.1 繼承 Thread 類建立執行緒

使用繼承 Thread 類建立執行緒的步驟如下:

  1. 新建一個類繼承 Thread 類,並重寫 Thread 類的 run() 方法。
  2. 建立 Thread 子類的例項。
  3. 呼叫該子類例項的 start() 方法啟動該執行緒。

程式碼舉例如下:

public class ThreadDemo extends Thread {
	
	// 1. 新建一個類繼承 Thread 類,並重寫 Thread 類的 run() 方法。
	@Override
	public void run() {
		System.out.println("Hello Thread");
	}
	
	public static void main(String[] args) {
		
		// 2. 建立 Thread 子類的例項。
		ThreadDemo threadDemo = new ThreadDemo();
		// 3. 呼叫該子類例項的 start() 方法啟動該執行緒。
		threadDemo.start();
		
	}

}

列印結果如下:

Hello Thread

2.2 實現 Runnable 介面建立執行緒

使用實現 Runnable 介面建立執行緒步驟是:

  1. 建立一個類實現 Runnable 介面,並重寫該介面的 run() 方法。
  2. 建立該實現類的例項。
  3. 將該例項傳入 Thread(Runnable r) 構造方法中建立 Thread 例項。
  4. 呼叫該 Thread 執行緒物件的 start() 方法。

程式碼舉例如下:


public class RunnableDemo implements Runnable {

	// 1. 建立一個類實現 Runnable 介面,並重寫該介面的 run() 方法。
	@Override
	public void run
() { System.out.println("Hello Runnable"); } public static void main(String[] args) { // 2. 建立該實現類的例項。 RunnableDemo runnableDemo = new RunnableDemo(); // 3. 將該例項傳入 Thread(Runnable r) 構造方法中建立 Thread 例項。 Thread thread = new Thread(runnableDemo); // 4. 呼叫該 Thread 執行緒物件的 start() 方法。 thread.start(); } }

列印結果如下:

Hello Runnable

2.3 使用 Callable 和 FutureTask 建立執行緒

使用這種方法建立的執行緒可以獲取一個返回值,使用實現 Callable 和 FutureTask 建立執行緒步驟是:

  1. 建立一個類實現 Callable 介面,並重寫 call() 方法。
  2. 建立該 Callable 介面實現類的例項。
  3. 將 Callable 的實現類例項傳入 FutureTask(Callable callable) 構造方法中建立 FutureTask 例項。
  4. 將 FutureTask 例項傳入 Thread(Runnable r) 構造方法中建立 Thread 例項。
  5. 呼叫該 Thread 執行緒物件的 start() 方法。
  6. 呼叫 FutureTask 例項物件的 get() 方法獲取返回值。

程式碼舉例如下:

public class CallableDemo implements Callable<String> {

	// 1. 建立一個類實現 Callable 介面,並重寫 call() 方法。
	@Override
	public String call() throws Exception {
		System.out.println("CallableDemo is Running");
		return "Hello Callable";
	}
	
	public static void main(String[] args) {
		
		// 2. 建立該 Callable 介面實現類的例項。
		CallableDemo callableDemo = new CallableDemo();
		
		// 3. 將 Callable 的實現類例項傳入 FutureTask(Callable<V> callable) 構造方法中建立 FutureTask 例項。
		FutureTask<String> futureTask = new FutureTask<>(callableDemo);
		
		// 4. 將 FutureTask 例項傳入 Thread(Runnable r) 構造方法中建立 Thread 例項。
		Thread thread = new Thread(futureTask);
		
		// 5. 呼叫該 Thread 執行緒物件的 start() 方法。
		thread.start();
		
		// 6. 呼叫 FutureTask 例項物件的 get() 方法獲取返回值。
		try {
			System.out.println(futureTask.get());
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

}

列印結果如下:

CallableDemo is Running
Hello Callable

3. 執行緒的生命週期

當一個執行緒開啟之後,它會遵循一定的生命週期,它要經過新建,就緒,執行,阻塞和死亡這五種狀態,理解執行緒的生命週期有助於理解後面的相關的執行緒知識。

3.1 新建狀態

這個狀態的意思就是執行緒剛剛被創建出來,這時候的執行緒並沒有任何執行緒的動態特徵。

3.2 就緒狀態

當執行緒物件呼叫 start() 方法後,該執行緒就處於就緒狀態。處於這個狀態中的執行緒並沒有開始執行,只是表示這個執行緒可以運行了。

3.3 執行狀態

處於就緒狀態的執行緒獲得了 CPU 後,開始執行 run() 方法,這個執行緒就處於執行狀態。

3.4 阻塞狀態

當執行緒被暫停後,這個執行緒就處於阻塞狀態。

3.5 死亡狀態

當執行緒被停止後,這個執行緒就處於死亡狀態。

其實掌握多執行緒最主要的就是要熟悉控制執行緒的狀態,讓各個執行緒能更好的為我們的服務,下面就來講解控制執行緒的方法。

4. 控制執行緒

4.1 sleep()

4.1.1 執行緒生命週期的變化

sleep()

4.1.2 方法預覽

public static native void sleep(long millis)
public static void sleep(long millis, int nanos)

該方法的意思就是讓正在執行狀態的執行緒到阻塞狀態,而這個時間就是執行緒處於阻塞狀態的時間。millis 是毫秒的意思,nanos 是毫微秒。

4.1.3 程式碼舉例

public class SleepDemo {
	
	public static void main(String[] args) throws Exception {
		
		for(int i = 0; i < 10; i++) {
			System.out.println("Hello Thread Sleep");
			Thread.sleep(1000);
		}
		
	}

}

以上程式碼執行後每隔一秒就輸出 Hello Thread Sleep。

4.2 執行緒優先順序

4.2.1 方法預覽

public final void setPriority(int newPriority)
public final int getPriority()

從方法名就可以知道,以上兩個方法分別就是設定和獲得優先順序的。值得注意的是優先順序是在 1~10 範圍內,也可以使用以下三個靜態變數設定:

  • MAX_PRIORITY:優先順序為 10
  • NORM_PRIORITY:優先順序為 5
  • MIN_PRIORITY:優先順序為 1

4.3 yield()

4.3.1 執行緒生命週期的變化

yield()

4.3.2 方法預覽

public static native void yield();

這個方法的意思就是讓正在執行的執行緒回到就緒狀態,並不會阻塞執行緒。可能會發生一種情況就是,該執行緒呼叫了 yield() 方法後,執行緒排程器又會繼續呼叫該執行緒。這個方法要注意的是它只會讓步給比它優先順序高的或者和它優先順序相同並處在就緒狀態的執行緒。

4.3.3 程式碼舉例

public class YieldDemo extends Thread {

	@Override
	public void run() {

		for (int i = 0; i < 50; i++) {
			System.out.println(getName() + " " + i);

			if (i == 20) {
				Thread.yield();
			}
		}

	}

	public static void main(String[] args) {

		YieldDemo yieldDemo1 = new YieldDemo();
		YieldDemo yieldDemo2 = new YieldDemo();

		yieldDemo1.start();
		yieldDemo2.start();

	}

}

程式碼輸出結果:

Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 3
Thread-1 4
Thread-1 5
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
Thread-1 10
Thread-1 11
Thread-1 12
Thread-1 13
Thread-1 14
Thread-0 0
Thread-0 1
Thread-0 2
Thread-1 15
Thread-0 3
Thread-1 16
Thread-1 17
Thread-1 18
Thread-1 19
Thread-1 20
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-1 21
Thread-0 8
Thread-1 22
Thread-0 9
Thread-1 23
Thread-0 10
Thread-0 11
Thread-0 12
Thread-1 24
Thread-1 25
Thread-0 13
Thread-1 26
Thread-1 27
Thread-0 14
Thread-0 15
Thread-1 28
Thread-0 16
Thread-0 17
Thread-0 18
Thread-1 29
Thread-0 19
Thread-0 20
Thread-1 30
Thread-1 31
Thread-0 21
Thread-1 32
Thread-1 33
Thread-1 34
Thread-1 35
Thread-1 36
Thread-1 37
Thread-1 38
Thread-1 39
Thread-1 40
Thread-1 41
Thread-1 42
Thread-1 43
Thread-1 44
Thread-1 45
Thread-1 46
Thread-1 47
Thread-1 48
Thread-1 49
Thread-0 22
Thread-0 23
Thread-0 24
Thread-0 25
Thread-0 26
Thread-0 27
Thread-0 28
Thread-0 29
Thread-0 30
Thread-0 31
Thread-0 32
Thread-0 33
Thread-0 34
Thread-0 35
Thread-0 36
Thread-0 37
Thread-0 38
Thread-0 39
Thread-0 40
Thread-0 41
Thread-0 42
Thread-0 43
Thread-0 44
Thread-0 45
Thread-0 46
Thread-0 47
Thread-0 48
Thread-0 49

從列印結果就可以看到列印 Thread-1 20的時候,下一個執行的就是 Thread-0 4。列印 Thread-20 的時候,下一個執行的就是 Thread-1 30。但是要說明的是,不是每次的列印結果都是一樣的,因為前面說過執行緒呼叫 yield() 方法後,執行緒排程器有可能會繼續啟動該執行緒。

4.4 join()

4.4.1 執行緒生命週期的變化

join()

4.4.2 方法預覽

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException

這個方法其實要有兩個執行緒,也就是一個執行緒的執行緒執行體中有另一個執行緒在呼叫 join() 方法。舉個例子,Thread1 的 run() 方法執行體中有 Thread2 在呼叫 join(),這時候 Thread1 就會被阻塞,必須要等到 Thread2 的執行緒執行完成或者join() 方法的時間到後才會繼續執行。

4.4.3 join() 程式碼舉例


public class JoinDemo extends Thread {
	
	@Override
	public void run() {
		for(int i = 0; i < 50; i++) {
			
			System.out.println(getName() + " " + i);
		}
	}
	
	public static void main(String[] args) throws Exception {
		
		JoinDemo joinDemo = new JoinDemo();
		
		for(int i = 0; i < 50; i++) {
			
			if(i == 20) {
				
				joinDemo.start();
				joinDemo.join();
				
			}
			
			System.out.println(Thread.currentThread().getName() + " " + i);
			
		}
		
		
	}

}

程式碼輸出的結果:

main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
main 10
main 11
main 12
main 13
main 14
main 15
main 16
main 17
main 18
main 19
main 20
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
Thread-0 9
Thread-0 10
Thread-0 11
Thread-0 12
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21
Thread-0 22
Thread-0 23
Thread-0 24
Thread-0 25
Thread-0 26
Thread-0 27
Thread-0 28
Thread-0 29
Thread-0 30
Thread-0 31
Thread-0 32
Thread-0 33
Thread-0 34
Thread-0 35
Thread-0 36
Thread-0 37
Thread-0 38
Thread-0 39
Thread-0 40
Thread-0 41
Thread-0 42
Thread-0 43
Thread-0 44
Thread-0 45
Thread-0 46
Thread-0 47
Thread-0 48
Thread-0 49
main 21
main 22
main 23
main 24
main 25
main 26
main 27
main 28
main 29
main 30
main 31
main 32
main 33
main 34
main 35
main 36
main 37
main 38
main 39
main 40
main 41
main 42
main 43
main 44
main 45
main 46
main 47
main 48
main 49

以上的程式碼其實一個兩個執行緒,一個是 Thread-0,另一個就是 main,main 就是主執行緒的意思。從列印結果可以看到,主執行緒執行到 main 20 的時候,就開始執行 Thread-0 0,直到 Thread-0 執行完畢,main 才繼續執行。

4.4.4 join(long millis) 程式碼舉例

public class JoinDemo extends Thread {
	
	@Override
	public void run() {
		for(int i = 0; i < 50; i++) {
			
			System.out.println(getName() + " " + i);
		}
	}
	
	public static void main(String[] args) throws Exception {
		
		JoinDemo joinDemo = new JoinDemo();
		
		for(int i = 0; i < 50; i++) {
			
			System.out.println(Thread.currentThread().getName() + " " + i);
			
			if(i == 20) {
				
				joinDemo.start();
				joinDemo.join(1);
				
			}
			
		}
		
		
	}

}

列印結果:

main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
main 10
main 11
main 12
main 13
main 14
main 15
main 16
main 17
main 18
main 19
main 20
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
Thread-0 9
Thread-0 10
Thread-0 11
Thread-0 12
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21
Thread-0 22
Thread-0 23
Thread-0 24
Thread-0 25
Thread-0 26
Thread-0 27
Thread-0 28
Thread-0 29
Thread-0 30
Thread-0 31
Thread-0 32
Thread-0 33
main 21
main 22
Thread-0 34
main 23
Thread-0 35
Thread-0 36
main 24
main 25
main 26
Thread-0 37
main 27
Thread-0 38
main 28
Thread-0 39
main 29
Thread-0 40
main 30
Thread-0 41
main 31
Thread-0 42
main 32
main 33
main 34
main 35
Thread-0 43
Thread-0 44
Thread-0 45
main 36
Thread-0 46
main 37
main 38
main 39
main 40
main 41
main 42
main 43
main 44
main 45
main 46
main 47
main 48
main 49
Thread-0 47
Thread-0 48
Thread-0 49

其實這個的程式碼和 4.4.3 節的程式碼基本一樣,就是將 join() 改成 join(1) ,可以看到 main 並沒有等到 Thread-0 執行完就開始重新執行了。

4.5 後臺執行緒

4.5.1 方法預覽

public final void setDaemon(boolean on)
public final boolean isDaemon()

這個方法就是將執行緒設定為後臺執行緒,後臺執行緒的特點就是當前臺執行緒全部執行結束後,後臺執行緒就會隨之結束。此方法設定為 true 時,就是將執行緒設定為後臺執行緒。而 isDaemon() 就是返回此執行緒是否為後臺執行緒。

4.5.2 程式碼舉例


public class DaemonDemo extends Thread {

	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println(getName() + " "+ isDaemon() + " " + i);
		}
	}
	
	public static void main(String[] args) {
		
		DaemonDemo daemonDemo = new DaemonDemo();
		
		daemonDemo.setDaemon(true);
		
		daemonDemo.start();
		
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + " " + i);
		}
		
	}
	
}

列印結果:

main 0
main 1
main 2
main 3
main 4
Thread-0 true 0
main 5
main 6
main 7
main 8
main 9
Thread-0 true 1
Thread-0 true 2
Thread-0 true 3
Thread-0 true 4
Thread-0 true 5
Thread-0 true 6
Thread-0 true 7
Thread-0 true 8
Thread-0 true 9
Thread-0 true 10
Thread-0 true 11

從列印結果可以看到 main 執行完後,Thread-0 沒有執行完畢就結束了。

5. 一些注意點

5.1 sleep() 和 yield() 區別

作用處 sleep() yield()
給其他執行緒執行機會 會給其他執行緒執行機會,不會理會其他執行緒的優先順序 只會給優先順序相同,或者優先順序更高的執行緒執行機會
影響當前執行緒的狀態 從阻塞到就緒狀態 直接進入就緒狀態
異常 需要丟擲 InterruptedException 不需要丟擲任何異常

相關推薦

執行——執行基本概念

0. 簡介 這個系列開始來講解 Java 多執行緒的知識,這節就先講解多執行緒的基本知識。 1. 程序與執行緒 1.1 什麼是程序? 程序就是在執行過程中的程式,就好像手機執行中的微信,QQ,這些就叫做程序。 1.2 什麼是執行緒? 執行緒就是程序的執行單元,就好像一個音樂軟體可以聽音樂,下載音樂,這些任務

JS - Promise使用1(基本概念、使用優點)

一、promises相關概念 promises 的概念是由 CommonJS 小組的成員在 Promises/A 規範中提出來的。   1,then()方法介紹 根據 Promise/A 規範,promi

幾個基本概念“標準差&標準誤差,方差&均方差”

對於從事資料工作的人來說,經常需要用到方差、標準差、均方差等概念,但即使是一個數學專業的畢業生(比如我自己),經常也會被這幾個概念弄得頭暈腦脹,使用的時候也是清楚的少,碰運氣的多。 這裡,我

lotou一:基本概念

lotou是一個基於golang的支援分散式的輕量級遊戲伺服器框架,主要提供遊戲伺服器叢集的訊息轉發程式碼倉庫 lotou提供了三種不同的訊息傳送方式: 1.Send 用於普通的訊息推送,不需要返回,傳送之後就不再關注 2.Request 非同步非阻塞請

Spark Streaming----概述、基本概念、效能調優

本文章主要講述SparkStreaming概念原理、基本概念、以及調優等一些知識點。1      概述1.1  SparkStreaming是什麼Spark Streaming 是個批處理的流式(實時)計算框架。其基本原理是把輸入資料以某一時間間隔批量的處理,當批處理間隔縮短

執行(二)

多執行緒詳解(二) 在正式介紹執行緒建立的第二種方法之前,我們接著多執行緒詳解(一),講一下:對執行緒的記憶體圖、執行緒的狀態,為下面的學習打下基礎,小夥伴們不要急喲!! 一、多執行緒執行的記憶體圖(ps.博主沒有找到合適的畫圖工具,歡迎大神們貢獻啊) class pers

執行(一)

[多執行緒詳解(一)](http://www.neilx.com) 一、概念準備 1、程序 (1)直譯:正在進行中的程式 (2)解釋:執行一個程式時,會在記憶體中為程式開闢空間,這個空間就是一個程序。 (3)注意:一個程序中不可能沒有執行緒,只有有了執行緒才能執行; 程序只

面試題之——執行

多執行緒作為Java中很重要的一個知識點,在此還是有必要總結一下的。 一.執行緒的生命週期及五種基本狀態 關於Java中執行緒的生命週期,首先看一下下面這張較為經典的圖: 上圖中基本上囊括了Java中多執行緒各重要知識點。掌握了上圖中的各知識點,Java中的多執行緒也就基本上掌握了。主

iOS開發執行

在iOS開發中,多執行緒開發是非常重要的核心之一,這篇文章和大家分享一下多執行緒的進階-死鎖. iOS有三種多執行緒程式設計的技術,分別是:(一)NSThread(二)Cocoa NSOperation(三)GCD(全稱:Grand Central Dispatch) 如果你對多執行緒

Java執行(面試回顧)

1,什麼是多執行緒 一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務,多執行緒能滿足程式設計師編寫高效率的程式來達到充分利用 CPU 的目的。 2,執行緒的生命週期   執行緒的五種基本狀態 新建狀態(New):執行緒物件建立後,就是這種狀態。

Swift3.0 GCD執行

                                                                                          GCD思維導圖             GCD(Grand Central Dispatch

flask如何開啟執行

flask開啟多執行緒 在我之前寫的'flask中current_app、g、request、session原始碼的深究和理解'一文中解釋了flask如何支援多執行緒 主要通過兩個類來實現,LocalStack和Local,在Local中有兩個屬性,__storage__和__ident_func_

C#執行

C#多執行緒程式設計 一、使用執行緒的理由 1、可以使用執行緒將程式碼同其他程式碼隔離,提高應用程式的可靠性。 2、可以使用執行緒來簡化編碼。 3、可以使用執行緒來實現併發執行。 二、基本知識 1、程序與執行緒:程序作為作業系統執行程式的基本單位,擁有應用程式的資

【Boost】boost庫中thread執行5——談談執行中斷

執行緒不是在任意時刻都可以被中斷的。如果將執行緒中函式中的sleep()睡眠等待去掉,那麼即使在主執行緒中呼叫interrupt()執行緒也不會被中斷。 thread庫預定義了若干個執行緒的中斷點,只有當執行緒執行到中斷點的時候才能被中斷,一個執行緒可以擁有任意多箇中斷點。

Python執行

一、程序與執行緒關係 一個程序至少包含一個執行緒。 二、執行緒基礎 1、執行緒的狀態 執行緒有5種狀態,狀態轉換的過程如下圖所示: 2、執行緒同步(鎖) 多執行緒的優勢在於可以同時執行多個任務(至少感覺起來是這樣)。但是當執行

【Boost】boost庫中thread執行3——細說lock_guard

boost::lock_guard可以說是一種比boost::unique_lock輕量級的lock, 簡單一些場景可以用它就行了。 看看它的原始碼也很簡單:template<typename Mutex> class lock_guard { private:

GCD實踐——序列佇列/併發佇列與iOS執行

       GCD(Grand Central Dispatch),是蘋果提供的一個解決多執行緒開發的解決方案。GCD會自動管理執行緒的生命週期(建立執行緒,排程任務,銷燬執行緒),完全不需要我們管理,我們只需要告訴幹什麼就行。同時GCD使用block來進行任務的執行,用起

【Boost】boost庫中thread執行1

1. 概述 執行緒就是,在同一程式同一時間內允許執行不同函式的離散處理佇列。 這使得一個長時間去進行某種特殊運算的函式在執行時不阻礙其他的函式變得十分重要。 執行緒實際上允許同時執行兩種函式,而這兩個函式不必相互等待。 一旦一個應用程式啟動,它僅包含一個預設執行緒。 此執行

Objective-C執行(NSThread、NSOperation、GCD)

程序和執行緒 程式:一個由原始碼生成的可執行應用(比如qq,微信…) 程序:程序是指在系統中正在執行的一個應用程式。一個正在執行的程式可以看成一個程序,程序負責去向手機系統申請資源,同時將這些資源排程給我們的執行緒 執行緒:1個程序要想執行任務,必須得有執

(八) Java執行之阻塞佇列BlockingQueue及佇列優先順序

阻塞佇列 阻塞佇列與普通佇列的區別在於當佇列是空時從佇列中獲取元素的操作將會被阻塞,或者當佇列是滿時往佇列裡新增元素的操作會被阻塞。試圖從空的阻塞佇列中獲取元素的執行緒將會被阻塞,直到其他的執行緒往空的佇列插入新的元素,同樣試圖往已滿的阻塞佇列中新增新元素的執