【搞定Java併發程式設計】第3篇:多執行緒概述~上篇
上一篇:併發基礎概述:https://blog.csdn.net/pcwl1206/article/details/84833911
目 錄:
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。
新建 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(終止)。
狀態名稱 | 說明 |
---|---|
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