Java基礎Day08(多執行緒)
多執行緒
1. 執行緒
1.1 什麼是執行緒:
程式中負責執行的哪個東東就叫做執行緒(執行路線,進程內部的執行序列或者說是進程的子任務)
多執行緒執行時,在棧記憶體中,每一個執行執行緒都有自己所屬的棧記憶體空間。進行方法的壓棧和彈棧。
1.2 流程圖:
1.3 自定義多執行緒:
public class MyThread extends Thread{ /* * 利用繼承中的特點 * 將執行緒名稱傳遞 進行設置 */ public MyThread(String name){ super(name); } /* * 重寫run方法 * 定義執行緒要執行的程式碼 */ public void run(){ for (int i = 0; i < 20; i++) { //getName()方法 來自父親 System.out.println(getName()+i); }
}
}
//測試類
public class Demo {
public static void main(String[] args) {
System.out.println(“這裡是main執行緒”);
MyThread mt = new MyThread(“小強”);
mt.start();//開啟了一個新的執行緒
for (int i = 0; i < 20; i++) {
System.out.println(“旺財:”+i);
}
}
}
1.4 並發與並行
- 並發:指兩個或多個事件在同一個時間段內發生。
- 並行:指兩個或多個事件在同一時刻發生(同時發生)。
注意:單核處理器的電腦肯定是不能並行的處理多個任務的,只能是多個任務在單個CPU上並發運行。同
理,執行緒也是一樣的,從宏觀角度上理解執行緒是並行運行的,但是從微觀角度上分析卻是串列運行的,即一個
執行緒一個執行緒的去運行,當系統只有一個CPU時,執行緒會以某種順序執行多個執行緒,我們把這種情況稱之為
執行緒調度。
1.5 執行緒與進程
進程:是指一個記憶體中運行的應用程式,每個進程都有一個獨立的記憶體空間,一個應用程式可以同時運行多
個進程;進程也是程式的一次執行過程,是系統運行程式的基本單位;系統運行一個程式即是一個進程從創
建、運行到消亡的過程。
執行緒:執行緒是進程中的一個執行單元,負責當前進程中程式的執行,一個進程中至少有一個執行緒。一個進程
中是可以有多個執行緒的,這個應用程式也可以稱之為多執行緒程式。
簡而言之:一個程式運行後至少有一個進程,一個進程中可以包含多個執行緒
1.6 執行緒調度:
- 分時調度 : 所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間。
- 搶佔式調度 : 優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個(執行緒隨機性),Java使用的為搶佔式調度。設置執行緒的優先順序
1.7 搶佔式調度詳解
大部分作業系統都支援多進程並發運行,現在的作業系統幾乎都支援同時運行多個程式。比如:現在我
們上課一邊使用編輯器,一邊使用錄屏軟體,同時還開著畫圖板,dos窗口等軟體。此時,這些程式是
在同時運行,」感覺這些軟體好像在同一時刻運行著「。
實際上,CPU(中央處理器)使用搶佔式調度模式在多個執行緒間進行著高速的切換。對於CPU的一個核而
言,某個時刻,只能執行一個執行緒,而 CPU的在多個執行緒間切換速度相對我們的感覺要快,看上去就是
在同一時刻運行。 其實,多執行緒程式並不能提高程式的運行速度,但能夠提高程式運行效率,讓CPU的
使用率更高。
1.8 Thread類
API中該類中定義了有關執行緒的一些方法,具體如下:
1.9 構造方法:
- public Thread() :分配一個新的執行緒對象。
- public Thread(String name) :分配一個指定名字的新的執行緒對象。
- public Thread(Runnable target) :分配一個帶有指定目標新的執行緒對象。
- public Thread(Runnable target,String name) :分配一個帶有指定目標新的執行緒對象並指定名字。
1.10 常用方法:
- public String getName() :獲取當前執行緒名稱。
- public void start() :導致此執行緒開始執行; Java虛擬機調用此執行緒的run方法。
- public void run() :此執行緒要執行的任務在此處定義程式碼。
- public static void sleep(long millis) :使當前正在執行的執行緒以指定的毫秒數暫停(暫時停止執行)。
- public static Thread currentThread() :返回對當前正在執行的執行緒對象的引用。
1.11 創建執行緒類
Java使用 java.lang.Thread 類代表執行緒,所有的執行緒對象都必須是Thread類或其子類的實例。每個執行緒的作用是完成一定的任務,實際上就是執行一段程式流即一段順序執行的程式碼。Java使用執行緒執行體來代表這段程式流。
Java中通過繼承Thread類來創建並啟動多執行緒的步驟如下:
1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了執行緒需要完成的任務,因此把run()方法稱為執行緒執行體。
2. 創建Thread子類的實例,即創建了執行緒對象
3. 調用執行緒對象的start()方法來啟動該執行緒
測試類 public class Demo01 { public static void main(String[] args) { //創建自定義執行緒對象 MyThread mt = new MyThread("新的執行緒!"); //開啟新執行緒 mt.start(); //在主方法中執行for循環 for (int i = 0; i < 10; i++) { System.out.println("main執行緒!"+i); } } } 自定義執行緒類: public class MyThread extends Thread { //定義指定執行緒名稱的構造方法
public MyThread(String name) { //調用父類的String參數的構造方法,指定執行緒的名稱 super(name);
/**
* 重寫run方法,完成該執行緒執行的邏輯
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+”:正在執行!”+i);
}
}
}
}
1.12 創建執行緒方式二
步驟如下:
1. 定義Runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體。
2. 創建Runnable實現類的實例,並以此實例作為Thread的target來創建Thread對象,該Thread對象才是真正的執行緒對象。
3. 調用執行緒對象的start()方法來啟動執行緒。
public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } } public class Demo { public static void main(String[] args) { //創建自定義類對象 執行緒任務對象 MyRunnable mr = new MyRunnable(); //創建執行緒對象 Thread t = new Thread(mr, "小強"); t.start(); for (int i = 0; i < 20; i++) { System.out.println("旺財 " + i); } } }
- 通過實現Runnable介面,使得該類有了多執行緒類的特徵。
- run()方法是多執行緒程式的一個執行目標。
- 所有的多執行緒程式碼都在run方法裡面。
- Thread類實際上也是實現了Runnable介面的類。
1.13 Thread和Runnable的區別
如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable介面的話,則很容易的實現資源共享。
1.14 實現Runnable介面比繼承Thread類所具有的優勢:
1. 適合多個相同的程式程式碼的執行緒去共享同一個資源。
2. 可以避免java中的單繼承的局限性。
3. 增加程式的健壯性,實現解耦操作,程式碼可以被多個執行緒共享,程式碼和執行緒獨立。
4. 執行緒池只能放入實現Runable或Callable類執行緒,不能直接放入繼承Thread的類。
擴充:在java中,每次程式運行至少啟動2個執行緒。一個是main執行緒,一個是垃圾收集執行緒。因為每當使用命Java令執行一個類的時候,都會啟動一個JVM,每一個JVM其實在就是在作業系統中啟動了一個進程。
1.15 匿名內部類方式實現執行緒的創建
使用執行緒的內匿名內部類方式,可以方便的實現每個執行緒執行不同的執行緒任務操作。
使用匿名內部類的方式實現Runnable介面,重新Runnable介面中的run方法:
public class NoNameInnerClassThread {
public static void main(String[] args) {
// new Runnable(){
// public void run(){
// for (int i = 0; i < 20; i++) {
// System.out.println("張宇:"+i);
// }
// }
// }; //‐‐‐這個整體 相當於new MyRunnable()
Runnable r = new Runnable(){
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("張宇:"+i);
} }
};
new Thread(r).start();
for (int i = 0; i < 20; i++) {
System.out.println("費玉清:"+i);
} } }
2.1 執行緒安全
如果有多個執行緒在同時運行,而這些執行緒可能會同時運行這段程式碼。程式每次運行結果和單執行緒運行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。
2.2 執行緒同步
使用多個執行緒訪問同一資源的時候,且多個執行緒中對資源有寫的操作,就容易出現執行緒安全問題。
-
同步程式碼塊。
-
synchronized 關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
- 格式:synchronized(同步鎖){需要同步操作的程式碼}
-
/* * 執行賣票操作 */ @Override public void run() { //每個窗口賣票的操作 //窗口 永遠開啟 while(true){ synchronized (lock) { if(ticket>0){//有票 可以賣 //出票操作 //使用sleep模擬一下出票時間 try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto‐generated catch block e.printStackTrace(); } //獲取當前執行緒對象的名字 String name = Thread.currentThread().getName(); System.out.println(name+"正在賣:"+ticket‐‐); } } }
-
-
同步方法。
- 同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A執行緒執行該方法的時候,其他執行緒只能在方法外等著。
- 格式: public synchronized void method(){可能會產生執行緒安全問題的程式碼}
-
/* * 執行賣票操作 */ @Override public void run() { //每個窗口賣票的操作 //窗口 永遠開啟 while(true){ sellTicket(); } } /* * 鎖對象 是 誰調用這個方法 就是誰 * 隱含 鎖對象 就是 this * */ public synchronized void sellTicket(){ if(ticket>0){//有票 可以賣 //出票操作 //使用sleep模擬一下出票時間 try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto‐generated catch block e.printStackTrace(); } //獲取當前執行緒對象的名字 String name = Thread.currentThread().getName(); System.out.println(name+"正在賣:"+ticket‐‐); } } }
- 同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A執行緒執行該方法的時候,其他執行緒只能在方法外等著。
-
鎖機制。
- java.util.concurrent.locks.Lock 機制提供了比synchronized程式碼塊和synchronized方法更廣泛的鎖定操作,同步程式碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象。Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了。
- public void lock() :加同步鎖。
- public void unlock() :釋放同步鎖
-
Lock lock = new ReentrantLock(); /* * 執行賣票操作 */ @Override public void run() { //每個窗口賣票的操作 //窗口 永遠開啟 while(true){ lock.lock(); if(ticket>0){//有票 可以賣 //出票操作 //使用sleep模擬一下出票時間 try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto‐generated catch block e.printStackTrace(); } //獲取當前執行緒對象的名字 String name = Thread.currentThread().getName(); System.out.println(name+"正在賣:"+ticket‐‐); } lock.unlock(); } } }
- java.util.concurrent.locks.Lock 機制提供了比synchronized程式碼塊和synchronized方法更廣泛的鎖定操作,同步程式碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象。Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了。
2.3 執行緒狀態
執行緒狀態
|
導致狀態發生條件 |
NEW(新建) | 執行緒剛被創建,但是並未啟動。還沒調用start方法。 |
Runnable(可運行)
|
執行緒可以在java虛擬機中運行的狀態,可能正在運行自己程式碼,也可能沒有,這取決於操作系統處理器。
|
Blocked(鎖阻塞)
|
當一個執行緒試圖獲取一個對象鎖,而該對象鎖被其他的執行緒持有,則該執行緒進入Blocked狀態;當該執行緒持有鎖時,該執行緒將變成Runnable狀態。
|
Waiting(無限等待)
|
一個執行緒在等待另一個執行緒執行一個(喚醒)動作時,該執行緒進入Waiting狀態。進入這個狀態後是不能自動喚醒的,必須等待另一個執行緒調用notify或者notifyAll方法才能夠喚醒。
|
TimedWaiting(計時等待)
|
同waiting狀態,有幾個方法有超時參數,調用他們將進入Timed Waiting狀態。這一狀態將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的常用方法有Thread.sleep 、Object.wait。
|
Teminated(被終止)
|
因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。
|