1. 程式人生 > >【搞定Java併發程式設計】第3篇:多執行緒概述~上篇

【搞定Java併發程式設計】第3篇:多執行緒概述~上篇

上一篇:併發基礎概述:https://blog.csdn.net/pcwl1206/article/details/84833911

目  錄:

1、什麼是執行緒

2、執行緒的建立

2.1、Thread和Runnable簡介

2.2、Thread和Runnable的異同點

2.3、Thread和Runnable的多執行緒示例

3、Run方法和Start方法的區別

4、執行緒的狀態


1、什麼是執行緒

執行緒(Thread)是一個物件(Object)。現代作業系統排程的最小單元就是執行緒。Java 執行緒是 Java 程序內允許多個同時進行的任務。該程序內併發的任務稱為為執行緒(Thread),一個程序裡至少一個執行緒。

在一個程序裡可以建立多個執行緒,這些執行緒都擁有各自的程式計數器、堆疊和區域性變數等屬性,並且能夠訪問共享的記憶體變數。

1.1  多執行緒

Java 程式採用多執行緒方式來支援大量的併發請求處理,程式如果在多執行緒方式執行下,其複雜度遠高於單執行緒序列執行。那麼多執行緒指的就是這個程式(一個程序)執行時產生了不止一個執行緒。

  • 為什麼要使用多執行緒?

1、適合多核處理器:一個執行緒執行在一個處理器核心上,那麼多執行緒可以分配到多個處理器核心上,更好地利用多核處理器;

2、更快的響應時間:將資料一致性不強的操作使用多執行緒技術(或者訊息佇列)加快程式碼邏輯處理,縮短響應時間;

3、更好的程式設計模型

  • 併發與並行的區別:

1、併發:類似單個 CPU ,通過 CPU 排程演算法等,處理多個任務的能力;

2、並行:類似多個 CPU ,同時並且處理相同多個任務的能力;


2、執行緒的建立

Java建立執行緒物件有兩種方法:

1、繼承Thread類建立執行緒物件;

2、實現Runnable介面建立執行緒物件。

2.1、Thread和Runnable簡介

  • Runnable:

Runnable是一個介面,該介面中只包含了一個run()方法,它的定義如下:

public interface Runnable{
    public abstract void run();
}

Runnable的作用是實現多執行緒。我們可以定義一個類A實現Runnable介面;然後,通過new Thread(new A())等方式新建執行緒。

  • Thread

Thread是一個類,它本身就實現了Runnable介面。它的宣告如下:

public class Thread implements Runnable{
    // ...
}

Thread的作用是:實現多執行緒。

2.2、Thread和Runnable的異同點

如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runnable介面的話,很容易實現資源共享。

實現Runnable介面比繼承Thread類具有以下優勢:

1、可以避免Java中的單繼承問題,Runnable的可擴充套件性更好;

2、Runnable還可以用於“資源共享”。即,多個執行緒都是基於某一個Runnable物件建立的,它們會共享Runnable物件上的資源。

2.3、Thread和Runnable的多執行緒示例

  • Thread的多執行緒示例
public class MyThread extends Thread {

	private int ticket = 10;
	
	public void run(){
		for(int i = 0; i < 20; i++){
			if(this.ticket > 0){
				System.out.println(this.getName() + " 賣票:ticket" + this.ticket--);
			}
		}
	}
	
	public static void main(String[] args) {
		
		// 啟動三個執行緒,每個執行緒各賣10張票
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		
		t1.start();
		t2.start();
		t3.start();
	}
}

執行結果: 

結果說明

1、MyThread繼承於Thread,它是自定義執行緒。每個MyThread都會賣出10張票。

2、 主執行緒main建立並啟動3個MyThread子執行緒。每個子執行緒都各自賣出了10張票。

  • Runnable的多執行緒示例
public class MyThread implements Runnable {

	private int ticket = 10;
	
	public void run(){
		for(int i = 0; i < 20; i++){
			if(this.ticket > 0){
				System.out.println(Thread.currentThread().getName() + " 賣票:ticket" + this.ticket--);
			}
		}
	}
	
	public static void main(String[] args) {
		
		MyThread mt = new MyThread();
		
		// 啟動三個執行緒,每個執行緒各賣10張票
		Thread t1 = new Thread(mt);
		Thread t2 = new Thread(mt);
		Thread t3 = new Thread(mt);
		
		t1.start();
		t2.start();
		t3.start();
	}
}

執行結果:

結果說明

1、 和上面“MyThread繼承於Thread”不同,這裡的MyThread實現了Runnable介面。

2、 主執行緒main建立並啟動3個子執行緒,而且這3個子執行緒都是基於“mt這個Runnable物件”而建立的。執行結果是這3個子執行緒一共賣出了10張票。這說明它們是共享了MyThread介面的。

  • 需要注意的幾個點:

1、main方法也是一個執行緒。在Java中所有的執行緒都是同時啟動的,至於哪個先執行、什麼時候執行,完全看誰先得到CPU資源了;

2、在Java中,每次程式至少會啟動2個執行緒。一個是main執行緒,另一個是垃圾收集執行緒。因為每當使用Java命令執行一個類的時候,實際上都會啟動一個Jvm,每一個Jvm實際上就是在作業系統中啟動了一個程序。


3、Run方法和Start方法的區別

1、start():它的作用是啟動一個新執行緒,新執行緒啟動後會執行相應的run()方法;start()方法不能被重複呼叫;

2、run():和普通的成員方法一樣,可以被重複呼叫。單獨呼叫run()的話,會在當前執行緒中執行run()方法,而不會啟動新執行緒。

public class MyThread extends Thread{

	public MyThread(String name){
		super(name);
	}
	
	public void run(){
		System.out.println(Thread.currentThread().getName() + " is running");
	}
	
	public static void main(String[] args) {
		
		Thread myThread = new MyThread("myThread");
		
		System.out.println(Thread.currentThread().getName() + " call myThread.run()");
		myThread.run();
		
		System.out.println(Thread.currentThread().getName() + " call myThread.start()");
		myThread.start();
	}
}

執行結果:

結果說明

1、Thread.currentThread().getName()是用於獲取“當前執行緒”的名字。當前執行緒是指正在cpu中排程執行的執行緒。

2、 myThread.run()是在“主執行緒main”中呼叫的,該run()方法直接執行在“主執行緒main”上。

3、myThread.start()會啟動“執行緒mythread”,“執行緒mythread”啟動之後,會呼叫run()方法;此時的run()方法是執行在“執行緒myThread”上。

  • Thread.java中start()方法的原始碼:
public synchronized void start() {
    // 如果執行緒不是"就緒狀態",則丟擲異常!
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    // 將執行緒新增到ThreadGroup中
    group.add(this);

    boolean started = false;
    try {
        // 通過start0()啟動執行緒
        start0();
        // 設定started標記
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

說明:start()實際上是通過本地方法start0()啟動執行緒的。而start0()會新執行一個執行緒,新執行緒會呼叫run()方法。

private native void start0();
  • Thread.java中run()方法的原始碼碼如下:
public void run() {
    if (target != null) {
        target.run();
    }
}

說明:target是一個Runnable物件。run()就是直接呼叫Thread執行緒的Runnable成員的run()方法,並不會新建一個執行緒。

  • 執行緒的執行

在執行上面兩種建立執行緒的程式碼後,JVM 執行了 main 函式執行緒,然後在主執行緒中執行建立了新的執行緒。正常情況下,所有執行緒執行到執行結束為止。除非某個執行緒中呼叫了 System.exit(1) 則被終止。

在實際開發中,一個請求到響應式是一個執行緒。但在這個執行緒中可以使用執行緒池建立新的執行緒,去執行任務。


4、執行緒的狀態

首先推薦一篇文章:Java執行緒到底有多少種狀態

本人也查看了Thread的原始碼,原始碼中定義了6種狀態:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

Thread原始碼中對執行緒狀態的定義

新建 MyThread類,列印執行緒物件屬性,程式碼如下:

public class MyThread extends Thread{

	@Override 
    public void run() {
        System.out.println("MyThread的執行緒例項正在執行任務");
    }
 
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
 
        System.out.print("MyThread的執行緒物件 \n"
                + "執行緒唯一識別符號:" + thread.getId() + "\n"
                + "執行緒名稱:" + thread.getName() + "\n"
                + "執行緒狀態:" + thread.getState() + "\n"
                + "執行緒優先順序:" + thread.getPriority());
    }
}

執行結果:

執行緒是一個物件,它有唯一識別符號 ID、名稱、狀態、優先順序等屬性。執行緒只能修改其優先順序和名稱等屬性 ,無法修改 ID 、狀態。ID 是 JVM 分配的,名字預設也為 Thread-XX,XX是一組數字。執行緒初始狀態為 NEW。

執行緒優先順序(priority)的範圍是 1 到 10 ,其中 1 是最低優先順序,10 是最高優先順序,預設的優先順序是5。不推薦改變執行緒的優先順序,如果業務需要,自然可以修改執行緒優先順序到最高,或者最低。可以通過:setPriority(int)方法來修改優先順序。但是需要說明的是作業系統可能不會理會你設定的優先順序,因此,程式的正確性不能依賴執行緒的優先順序高低

執行緒的狀態實現通過 Thread.State 常量類實現,有 6 種執行緒狀態:new(新建)、runnnable(可執行)、blocked(阻塞)、waiting(等待)、time waiting (定時等待) terminated(終止)

Java執行緒的狀態
狀態名稱 說明
NEW 初始狀態,執行緒被構建,但是還沒有呼叫start()方法
RUNNABLE 執行狀態,Java執行緒將作業系統中的就緒和執行兩種狀態籠統地稱為“執行中”
BLOCKED 阻塞狀態,表示執行緒阻塞與鎖
WAITING 等待狀態,表示執行緒進入等待狀態,進入該狀態表示當前執行緒需要等待其他執行緒做出一些特定動作(通知或中斷)
TIME_WAITING 超時等待狀態,該狀態不同於WAITING,它是可以在指定的時間自行返回的
TERMINATED 終止狀態,表示當前執行緒已經執行完畢

狀態轉換圖如下

執行緒狀態流程大致如下:

1、新建了一個執行緒物件,進入new狀態。例如,Thread thread = new Thread();

2、runnable叫“就緒狀態”。執行緒新建後,其他執行緒(比如main執行緒)呼叫了該物件的start()方法,從而來啟動該執行緒。Runnable狀態的執行緒位於可執行執行緒池中,等待被執行緒排程選中,獲取CPU的使用權;

3、runnable叫”可執行狀態“的執行緒獲得了CPU時間片(timeslice),執行程式碼。需要注意的是,執行緒只能從就緒狀態進入到執行狀態;

4、如果執行緒執行sleep、wait、join方法或者IO阻塞將會進入wait狀態或者blocked狀態;

5、執行緒執行完畢後,執行緒被執行緒佇列移除。最後為terminated狀態。


上一篇:併發基礎概述:https://blog.csdn.net/pcwl1206/article/details/84833911

參考及推薦:

1、併發基礎與Java多執行緒:https://blog.csdn.net/a724888/article/details/60867044

2、Java多執行緒系列目錄:https://www.cnblogs.com/skywang12345/p/java_threads_category.html