java----day26(多執行緒)
多執行緒
- 多執行緒介紹
程序:程序指正在執行的程式。確切的來說,當一個程式進入記憶體執行,即變成一個程序,程序是處於執行過程中的程式,並且具有一定獨立功能。
執行緒:執行緒是程序中的一個執行單元,負責當前程序中程式的執行,一個程序中至少有一個執行緒。一個程序中是可以有多個執行緒的,這個應用程式也可以稱之為多執行緒程式。
簡而言之:一個程式執行後至少有一個程序,一個程序中可以包含多個執行緒
- 程式執行原理
- 分時排程
所有執行緒輪流使用 CPU,平均分配每個執行緒佔用 CPU 的時間。
- 搶佔式排程
優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個(執行緒隨機性),Java使用的為搶佔式排程。
實際上,CPU(中央處理器)使用搶佔式排程模式在多個執行緒間進行著高速的切換。對於CPU的一個核而言,某個時刻,只能執行一個執行緒,而 CPU的在多個執行緒間切換速度相對我們的感覺要快,看上去就是在同一時刻執行。
其實,多執行緒程式並不能提高程式的執行速度,但能夠提高程式執行效率,讓CPU的使用率更高。
- 主執行緒
回想我們以前學習中寫過的程式碼,當我們在dos命令列中輸入java空格類名回車後,啟動JVM,並且載入對應的class檔案。虛擬機器並會從main方法開始執行我們的程式程式碼,一直把main方法的程式碼執行結束。如果在執行過程遇到迴圈時間比較長的程式碼,那麼在迴圈之後的其他程式碼是不會被馬上執行的。如下程式碼演示:
//未使用執行緒,語句按順序執行 class Demo{ String name; Demo(String name){ this.name = name; } void show() { for (int i=1;i<=10000 ;i++ ) { System.out.println("name="+name+",i="+i); } } } class ThreadDemo { public static void main(String[] args) { Demo d = new Demo("小強"); Demo d2 = new Demo("旺財"); d.show(); d2.show(); System.out.println("Hello World!"); } }
若在上述程式碼中show方法中的迴圈執行次數很多,這時在d.show();下面的程式碼是不會馬上執行的,並且在dos視窗會看到不停的輸出name=小強,i=值,這樣的語句。為什麼會這樣呢?
原因是:jvm啟動後,必然有一個執行路徑(執行緒)從main方法開始的,一直執行到main方法結束,這個執行緒在java中稱之為主執行緒。當程式的主執行緒執行時,如果遇到了迴圈而導致程式在指定位置停留時間過長,則無法馬上執行下面的程式,需要等待迴圈結束後能夠執行。
那麼,能否實現一個主執行緒負責執行其中一個迴圈,再由另一個執行緒負責其他程式碼的執行,最終實現多部分程式碼同時執行的效果?
能夠實現同時執行,通過Java中的多執行緒技術來解決該問題。
- Thread類
建立新執行執行緒有兩種方法。
- 一種方法是將類宣告為 Thread 的子類。該子類應重寫 Thread 類的 run 方法。建立物件,開啟執行緒。run方法相當於其他執行緒的main方法。
- 另一種方法是宣告一個實現 Runnable 介面的類。該類然後實現 run 方法。然後建立Runnable的子類物件,傳入到某個執行緒的構造方法中,開啟執行緒。
- 建立執行緒方式一繼承Thread類
建立執行緒的步驟:
1 定義一個類繼承Thread。
2 重寫run方法。
3 建立子類物件,就是建立執行緒物件。
4 呼叫start方法,開啟執行緒並讓執行緒執行,同時還會告訴jvm去呼叫run方法。
多執行緒的記憶體圖解
多執行緒執行時,在棧記憶體中,其實每一個執行執行緒都有一片自己所屬的棧記憶體空間。進行方法的壓棧和彈棧。
當執行執行緒的任務結束了,執行緒自動在棧記憶體中釋放了。但是當所有的執行執行緒都結束了,那麼程序就結束了
獲取執行緒名稱
- Thread.currentThread()獲取當前執行緒物件
- Thread.currentThread().getName();獲取當前執行緒物件的名稱
來主執行緒的名稱:main;自定義的執行緒:Thread-0,執行緒多個時,數字順延。如Thread-1......
進行多執行緒程式設計時,不要忘記了Java程式執行是從主執行緒開始,main方法就是主執行緒的執行緒執行內容。
- 建立執行緒方式—實現Runnable介面
建立執行緒的另一種方法是宣告實現 Runnable 介面的類。該類然後實現 run 方法。然後建立Runnable的子類物件,傳入到某個執行緒的構造方法中,開啟執行緒
- 建立執行緒的步驟。
1、定義類實現Runnable介面。
2、覆蓋介面中的run方法。。
3、建立Thread類的物件
4、將Runnable介面的子類物件作為引數傳遞給Thread類的建構函式。
5、呼叫Thread類的start方法開啟執行緒。
public class MyRunnable implements Runnable{
//定義執行緒要執行的run方法邏輯
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我的執行緒:正在執行!"+i);
}
}
}
實現Runnable介面,避免了繼承Thread類的單繼承侷限性。覆蓋Runnable介面中的run方法,將執行緒任務程式碼定義到run方法中。
建立Thread類的物件,只有建立Thread類的物件才可以建立執行緒。執行緒任務已被封裝到Runnable介面的run方法中,而這個run方法所屬於Runnable介面的子類物件,所以將這個子類物件作為引數傳遞給Thread的建構函式,這樣,執行緒物件建立時就可以明確要執行的執行緒的任務。
實現Runnable介面避免了單繼承的侷限性,所以較為常用。實現Runnable介面的方式,更加的符合面向物件,執行緒分為兩部分,一部分執行緒物件,一部分執行緒任務。繼承Thread類,執行緒物件和執行緒任務耦合在一起。一旦建立Thread類的子類物件,既是執行緒物件,有又有執行緒任務。實現runnable介面,將執行緒任務單獨分離出來封裝成物件,型別就是Runnable介面型別。Runnable介面對執行緒物件和執行緒任務進行解耦。
- 執行緒的匿名內部類使用
使用執行緒的內匿名內部類方式,可以方便的實現每個執行緒執行不同的執行緒任務操作
new Thread() {
public void run() {
for (int x = 0; x < 40; x++) {
System.out.println(Thread.currentThread().getName()
+ "...X...." + x);
}
}
}.start();
執行緒池
- 執行緒池概念
執行緒池,其實就是一個容納多個執行緒的容器,其中的執行緒可以反覆使用,省去了頻繁建立執行緒物件的操作,無需反覆建立執行緒而消耗過多資源。
我們詳細的解釋一下為什麼要使用執行緒池?
在java中,如果每個請求到達就建立一個新執行緒,開銷是相當大的。在實際使用中,建立和銷燬執行緒花費的時間和消耗的系統資源都相當大,甚至可能要比在處理實際的使用者請求的時間和資源要多的多。除了建立和銷燬執行緒的開銷之外,活動的執行緒也需要消耗系統資源。如果在一個jvm裡建立太多的執行緒,可能會使系統由於過度消耗記憶體或“切換過度”而導致系統資源不足。為了防止資源不足,需要採取一些辦法來限制任何給定時刻處理的請求數目,儘可能減少建立和銷燬執行緒的次數,特別是一些資源耗費比較大的執行緒的建立和銷燬,儘量利用已有物件來進行服務。
執行緒池主要用來解決執行緒生命週期開銷問題和資源不足問題。通過對多個任務重複使用執行緒,執行緒建立的開銷就被分攤到了多個任務上了,而且由於在請求到達時執行緒已經存在,所以消除了執行緒建立所帶來的延遲。這樣,就可以立即為請求服務,使用應用程式響應更快。另外,通過適當的調整執行緒中的執行緒數目可以防止出現資源不足的情況。
- 使用執行緒池方式--Runnable介面
通常,執行緒池都是通過執行緒池工廠建立,再呼叫執行緒池中的方法獲取執行緒,再通過執行緒去執行任務方法。
- Executors:執行緒池建立工廠類
- public static ExecutorService newFixedThreadPool(int nThreads):返回執行緒池物件
- ExecutorService:執行緒池類
- Future<?> submit(Runnable task):獲取執行緒池中的某一個執行緒物件,並執行
- Future介面:用來記錄執行緒任務執行完畢後產生的結果。執行緒池建立與使用
- 使用執行緒池中執行緒物件的步驟:
- 建立執行緒池物件
- 建立Runnable介面子類物件
- 提交Runnable介面子類物件
- 關閉執行緒池
- 使用執行緒池方式—Callable介面
- Callable介面:與Runnable介面功能相似,用來指定執行緒的任務。其中的call()方法,用來返回執行緒任務執行完畢後的結果,call方法可丟擲異常。
- ExecutorService:執行緒池類
- <T> Future<T> submit(Callable<T> task):獲取執行緒池中的某一個執行緒物件,並執行執行緒中的call()方法
- Future介面:用來記錄執行緒任務執行完畢後產生的結果。執行緒池建立與使用
- 使用執行緒池中執行緒物件的步驟:
- 建立執行緒池物件
- 建立Callable介面子類物件
- 提交Callable介面子類物件
- 關閉執行緒池
總結
建立執行緒的方式
方式1,繼承Thread執行緒類
- 步驟
- 自定義類繼承Thread類
- 在自定義類中重寫Thread類的run方法
- 建立自定義類物件(執行緒物件)
- 呼叫start方法,啟動執行緒,通過JVM,呼叫執行緒中的run方法
方式2,實現Runnable介面
- 步驟
- 建立執行緒任務類 實現Runnable介面
- 線上程任務類中 重寫介面中的run方法
- 建立執行緒任務類物件
- 建立執行緒物件,把執行緒任務類物件作為Thread類構造方法的引數使用
呼叫start方法,啟動執行緒,通過JVM,呼叫執行緒任務類中的run方法