螞蟻集團 2023 春招正式開啟:技術人才佔比超八成,首次新增密碼演算法、Golang 語言等崗位
阿新 • • 發佈:2022-03-10
多執行緒學習
一、多執行緒概念
1.1、多執行緒定義
-
程式:是為完成特定任務,用某種語言編寫的一組指令的集合,即指一段靜態的程式碼,靜態物件。
-
程序:是正在執行的程式
- 獨立性:程序是一個能獨立執行的基本單位,同時也是系統分配資源和排程的獨立單位
- 動態性:程序的實質是程式的一次執行過程,程序是動態產生,動態消亡的
- 併發性:任何程序都可以同其他程序一起併發執行
-
執行緒:是程序中的單個順序控制流,是一條執行路徑
- 單執行緒:一個程序如果只有一條執行路徑,則稱為單執行緒程式
- 多執行緒:一個程序如果有多條執行路徑,則稱為多執行緒程式
-
並行:在同一時刻,有多個指令在多個CPU上同時執行。
-
併發:在同一時刻,有多個指令在單個CPU上交替執行。
1.2、多執行緒的排程方式
- 分時排程模式:所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間。
- 搶佔排程模式:優先讓優先順序較高的執行緒使用 CPU,如果優先順序相同,那麼會隨機選擇一個。
- Java 使用的就是搶佔式排程模型,執行緒的執行是隨機的。
- 執行緒預設優先順序是 5,優先順序範圍是 1-10。
- 當執行的執行緒都是守護執行緒時,JVM 將退出。
二、實現多執行緒的多種方式
2.1、繼承 Thred 類
-
方法介紹
方法名 說明 void run() 線上程開啟後,此方法將被呼叫執行 void start() 使此執行緒開始執行,Java虛擬機器會呼叫run方法() -
實現步驟
- 定義一個類MyThread繼承Thread類
- 在MyThread類中重寫run()方法
- 建立MyThread類的物件
- 啟動執行緒
-
程式碼演示
public class MyThread extends Thread { @Override public void run() { for(int i=0; i<100; i++) { System.out.println(i); } } } public class MyThreadDemo { public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); // my1.run(); // my2.run(); //void start() 導致此執行緒開始執行; Java虛擬機器呼叫此執行緒的run方法 my1.start(); my2.start(); } }
- 注意:
- 從寫 run()方法:應為 run()是用來封裝被執行緒執行的程式碼。
- run():封裝被執行緒執行的程式碼,直接呼叫,相當於普通方法的呼叫。
- start():啟動執行緒,然後由 JVM 調動此執行緒的 run()方法。
2.2、實現 Runnable 介面
-
Thread構造方法
方法名 說明 Thread(Runnable target) 分配一個新的Thread物件 Thread(Runnable target, String name) 分配一個新的Thread物件和名稱 -
實現步驟
- 定義一個類MyRunnable實現Runnable介面
- 在MyRunnable類中重寫run()方法
- 建立MyRunnable類的物件
- 建立Thread類的物件,把MyRunnable物件作為構造方法的引數
- 啟動執行緒
-
程式碼演示
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
//建立MyRunnable類的物件
MyRunnable my = new MyRunnable();
//建立Thread類的物件,把MyRunnable物件作為構造方法的引數
//Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
//Thread(Runnable target, String name)
Thread t1 = new Thread(my,"坦克");
Thread t2 = new Thread(my,"飛機");
//啟動執行緒
t1.start();
t2.start();
}
}
2.3、實現 Callable 介面
-
方法介紹
方法名 說明 V call() 計算結果,如果無法計算結果,則丟擲一個異常 FutureTask(Callable callable) 建立一個 FutureTask,一旦執行就執行給定的 Callable V get() 如有必要,等待計算完成,然後獲取其結果 -
實現步驟
- 定義一個類MyCallable實現Callable介面
- 在MyCallable類中重寫call()方法
- 建立MyCallable類的物件
- 建立Future的實現類FutureTask物件,把MyCallable物件作為構造方法的引數
- 建立Thread類的物件,把FutureTask物件作為構造方法的引數
- 啟動執行緒
- 再呼叫get方法,就可以獲取執行緒結束之後的結果。
-
程式碼演示
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("跟女孩表白" + i);
}
//返回值就表示執行緒執行完畢之後的結果
return "答應";
}
}
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//執行緒開啟之後需要執行裡面的call方法
MyCallable mc = new MyCallable();
//Thread t1 = new Thread(mc);
//可以獲取執行緒執行完畢之後的結果.也可以作為引數傳遞給Thread物件
FutureTask<String> ft = new FutureTask<>(mc);
//建立執行緒物件
Thread t1 = new Thread(ft);
String s = ft.get();
//開啟執行緒
t1.start();
//String s = ft.get();
System.out.println(s);
}
}
2.4、使用執行緒池
- 執行緒池介紹
Executors 工具類 | 執行緒池的工廠類,用於建立並返回不同型別的執行緒池 |
---|---|
Executors.newCachedThreadPool() | 建立一個可根據需要建立新執行緒的執行緒池 |
Executors.newFixedThreadPool(n) | 建立一個可重用固定執行緒數的執行緒池 |
Executors.newSingleThreadExecutor() | 建立一個只有一個執行緒的執行緒池 |
Executors.newScheduledThreadPool(n | 建立一個執行緒池,它可安排在給定延遲後執行命令或者定期地執行 |
- 實現步驟
- 建立實現 runnable 或 callable 介面方式的物件
- 建立 executorservice 執行緒池
- 將建立好的實現了 runnable 介面的物件 放入 executorservice 物件的 execute 方法中執行
- 關閉執行緒池
- 程式碼演示
public class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i<=100;i++){
if (i % 2 ==0 )
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i<100; i++){
if(i%2==1){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args){
//建立固定執行緒個數為十個的執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//new一個Runnable介面的物件
NumberThread number = new NumberThread();
NumberThread1 number1 = new NumberThread1();
//執行執行緒,最多十個
executorService.execute(number1);
executorService.execute(number);//適合適用於Runnable
//executorService.submit();//適合使用於Callable
//關閉執行緒池
executorService.shutdown();
}
}
2.5、不同實現方式對比
1、三種不同實現方式對比
- 實現Runable、Callable介面
- 好處:擴充套件性強,實現該介面的同時還可以繼承其他的類
- 缺點: 程式設計相對複雜,不能直接使用Thread類中的方法
- 繼承 Thread 類
- 好處: 程式設計比較簡單,可以直接使用Thread類中的方法
- 缺點: 可以擴充套件性較差,不能再繼承其他的類
2、runnable 和 callable 有什麼區別
- Runnable 介面 run 方法無返回值;Callable 介面 call 方法有返回值,是個泛型,和Future、FutureTask配合可以用來獲取非同步執行的結果。
- Runnable 介面 run 方法只能丟擲執行時異常,且無法捕獲處理;Callable 介面 call 方法允許丟擲異常,可以獲取異常資訊。
- 注:Callalbe介面支援返回執行結果,需要呼叫FutureTask.get()得到,此方法會阻塞主程序的繼續往下執行,如果不呼叫不會阻塞。
三、Thred 中常見的方法
3.1、設定和獲取執行緒名稱
-
方法介紹
方法名 說明 void setName(String name) 將此執行緒的名稱更改為等於引數name String getName() 返回此執行緒的名稱 Thread currentThread() 返回對當前正在執行的執行緒物件的引用 -
程式碼演示
public class MyThread extends Thread { public MyThread() {} public MyThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName()+":"+i); } } } public class MyThreadDemo { public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); //void setName(String name):將此執行緒的名稱更改為等於引數 name my1.setName("高鐵"); my2.setName("飛機"); //Thread(String name) MyThread my1 = new MyThread("高鐵"); MyThread my2 = new MyThread("飛機"); my1.start(); my2.start(); //static Thread currentThread() 返回對當前正在執行的執行緒物件的引用 System.out.println(Thread.currentThread().getName()); } }
3.2、執行緒休眠
-
相關方法
方法名 說明 static void sleep(long millis) 使當前正在執行的執行緒停留(暫停執行)指定的毫秒數 -
程式碼演示
public class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "---" + i); } } } public class Demo { public static void main(String[] args) throws InterruptedException { /*System.out.println("睡覺前"); Thread.sleep(3000); System.out.println("睡醒了");*/ MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); t1.start(); t2.start(); } }
3.3、執行緒優先順序
-
執行緒排程
-
兩種排程方式
- 分時排程模型:所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間片
- 搶佔式排程模型:優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個,優先順序高的執行緒獲取的 CPU 時間片相對多一些
-
Java使用的是搶佔式排程模型
-
隨機性
假如計算機只有一個 CPU,那麼 CPU 在某一個時刻只能執行一條指令,執行緒只有得到CPU時間片,也就是使用權,才可以執行指令。所以說多執行緒程式的執行是有隨機性,因為誰搶到CPU的使用權是不一定的
-
-
優先順序相關方法
方法名 說明 final int getPriority() 返回此執行緒的優先順序 final void setPriority(int newPriority) 更改此執行緒的優先順序執行緒預設優先順序是5;執行緒優先順序的範圍是:1-10 -
程式碼演示
public class MyCallable implements Callable<String> { @Override public String call() throws Exception { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "---" + i); } return "執行緒執行完畢了"; } } public class Demo { public static void main(String[] args) { //優先順序: 1 - 10 預設值:5 MyCallable mc = new MyCallable(); FutureTask<String> ft = new FutureTask<>(mc); Thread t1 = new Thread(ft); t1.setName("飛機"); t1.setPriority(10); //System.out.println(t1.getPriority());//5 t1.start(); MyCallable mc2 = new MyCallable(); FutureTask<String> ft2 = new FutureTask<>(mc2); Thread t2 = new Thread(ft2); t2.setName("坦克"); t2.setPriority(1); //System.out.println(t2.getPriority());//5 t2.start(); } }
3.4、守護執行緒
-
相關方法
方法名 說明 void setDaemon(boolean on) 將此執行緒標記為守護執行緒,當執行的執行緒都是守護執行緒時,Java虛擬機器將退出 -
程式碼演示
public class MyThread1 extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + "---" + i); } } } public class MyThread2 extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + "---" + i); } } } public class Demo { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2(); t1.setName("女神"); t2.setName("備胎"); //把第二個執行緒設定為守護執行緒 //當普通執行緒執行完之後,那麼守護執行緒也沒有繼續執行下去的必要了. t2.setDaemon(true); t1.start(); t2.start(); } }
四、執行緒同步 //TODO
4.1、執行緒安全問題
1、執行緒安全問題定義
- 當多個執行緒對同一個物件的例項變數,做寫(修改)的操作時,可能會受到其他執行緒的干擾,發生執行緒安全的問題
2、安全問題出現的條件
- 是多執行緒環境
- 有共享資料
- 有多條語句操作共享資料
3、安全問題的特性
- 原子性(Atomic):
- 不可分割,訪問(讀,寫)某個共享變數的時候,從其他執行緒來看,該操作要麼已經執行完畢,要麼尚未發生。其他執行緒看不到當前操作的中間結果。 訪問同一組共享變數的原子操作是不能夠交錯的,如現實生活中從ATM取款。
java中有兩種方式實現原子性:
1.鎖 :鎖具有排他性,可以保證共享變數某一時刻只能被一個執行緒訪問。
2.CAS指令 :直接在硬體層次上實現,看做是一個硬體鎖。
-
可見性(visbility):
- 在多執行緒環境中,一個執行緒對某個共享變數更新之後,後續其他的執行緒可能無法立即讀到這個更新的結果。
- 如果一個執行緒對共享變數更新之後,後續訪問該變數的其他執行緒可以讀到更新的結果,稱這個執行緒對共享變數的更新對其他執行緒可見。否則稱這個執行緒對共享變數的更新對其他執行緒不可見。
- 多執行緒程式因為可見性問題可能會導致其他執行緒讀取到舊資料(髒資料)。
-
有序性(Ordering):
- 是指在什麼情況下一個處理器上執行的一個執行緒所執行的 記憶體訪問操作在另外一個處理器執行的其他執行緒來看是亂序的(Out of Order)
- 亂序: 是指記憶體訪問操作的順序看起來發生了變化。