第五章:多執行緒
一、多執行緒概述
1.1、理解程式、程序、執行緒
1.1.1、程式
是為完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的程式碼。
1.1.2、程序
程式的一次執行過程,或是正在執行的一個程式。
說明:
程序作為資源分配的單位,系統在執行時會為每個程序分配不同的記憶體區域。
1.1.3、執行緒
程序可進一步細化為執行緒,是一個程式內部的一條執行路徑。
說明:
執行緒作為排程和執行的單位,每個執行緒擁獨立的執行棧和程式計數器(pc),執行緒切換的開銷小。
1.1.4、補充說明
程序可以細化為多個執行緒。
每個執行緒,擁有自己獨立的:棧、程式計數器
多個執行緒,共享同一個程序中的結構:方法區、堆。
二、並行與併發
並行:多個CPU同時執行多個任務。比如:多個人同時做不同的事。
併發:一個CPU(採用時間片)同時執行多個任務。比如:秒殺、多個人做同一件事。
三、建立多執行緒的方式
3.1、繼承Thread類
3.1.1、具體步驟
① 建立一個繼承於Thread類的子類
② 重寫Thread類的run() --> 將此執行緒執行的操作宣告在run()中
③ 建立Thread類的子類的物件
④ 通過此物件呼叫start():①啟動當前執行緒 ② 呼叫當前執行緒的run()
3.1.2、問題說明
問題一:我們啟動一個執行緒,必須呼叫start(),不能呼叫run()的方式啟動執行緒。
問題二:如果再啟動一個執行緒,必須重新建立一個Thread子類的物件,呼叫此物件的start()。
3.1.3、程式碼
package com.zixue.java; //1. 建立一個繼承於Thread類的子類 class MyThread extends Thread { //2. 重寫Thread類的run() @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName()+ ":" + i); } } } } public class ThreadTest { public static void main(String[] args) { //3. 建立Thread類的子類的物件 MyThread t1 = new MyThread(); //4.通過此物件呼叫start():①啟動當前執行緒 ② 呼叫當前執行緒的run() t1.start(); //問題一:我們不能通過直接呼叫run()的方式啟動執行緒。 // t1.run(); //問題二:再啟動一個執行緒,遍歷100以內的偶數。不可以還讓已經start()的執行緒去執行。會報IllegalThreadStateException // t1.start(); //我們需要重新建立一個執行緒的物件 MyThread t2 = new MyThread(); t2.start(); //如下操作仍然是在main執行緒中執行的。 for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************"); } } } }
3.1.4、視窗賣票示例
建立三個視窗賣票,總票數為100張.使用繼承Thread類的方式。
package com.zixue.java; /** * 存線上程安全問題:待解決 */ class Window extends Thread{ private static int ticket = 100; @Override public void run() { while(true){ if(ticket > 0){ System.out.println(getName() + ":賣票,票號為:" + ticket); ticket--; }else{ break; } } } } public class WindowTest { public static void main(String[] args) { Window t1 = new Window(); Window t2 = new Window(); Window t3 = new Window(); t1.setName("視窗1"); t2.setName("視窗2"); t3.setName("視窗3"); t1.start(); t2.start(); t3.start(); } }
3.2、實現Runnable介面
3.2.1、具體步驟
① 建立一個實現了Runnable介面的類
② 實現類去實現Runnable中的抽象方法:run()
③ 建立實現類的物件
④ 將此物件作為引數傳遞到Thread類的構造器中,建立Thread類的物件
⑤ 通過Thread類的物件呼叫start()
3.2.2、程式碼
package com.zixue.java; //1. 建立一個實現了Runnable介面的類 class MThread implements Runnable{ //2. 實現類去實現Runnable中的抽象方法:run() @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest1 { public static void main(String[] args) { //3. 建立實現類的物件 MThread mThread = new MThread(); //4. 將此物件作為引數傳遞到Thread類的構造器中,建立Thread類的物件 Thread t1 = new Thread(mThread); t1.setName("執行緒1"); //5. 通過Thread類的物件呼叫start():① 啟動執行緒 ②呼叫當前執行緒的run()-->呼叫了Runnable型別的target的run() t1.start(); //再啟動一個執行緒,遍歷100以內的偶數 Thread t2 = new Thread(mThread); t2.setName("執行緒2"); t2.start(); } }
3.2.3、視窗賣票示例
package com.zixue.java; class Window1 implements Runnable{ private int ticket = 100; @Override public void run() { while(true){ if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket); ticket--; }else{ break; } } } } public class WindowTest1 { public static void main(String[] args) { Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("視窗1"); t2.setName("視窗2"); t3.setName("視窗3"); t1.start(); t2.start(); t3.start(); } }
3.3、兩種方式的對比
開發中:優先選擇:實現Runnable介面的方式。
原因:
① 實現的方式沒類的單繼承性的侷限性
② 實現的方式更適合來處理多個執行緒有共享資料的情況。
聯絡:public class Thread implements Runnable
相同點:
① 兩種方式都需要重寫run(),將執行緒要執行的邏輯宣告在run()中。
② 目前兩種方式,要想啟動執行緒,都是呼叫的Thread類中的start()。
四、Thread類中的常用方法
① start():啟動當前執行緒;呼叫當前執行緒的run()
② run(): 通常需要重寫Thread類中的此方法,將建立的執行緒要執行的操作宣告在此方法中。
③ currentThread():靜態方法,返回執行當前程式碼的執行緒。
④ getName():獲取當前執行緒的名字
⑤ setName():設定當前執行緒的名字
⑥ yield():釋放當前cpu的執行權
⑦ join():線上程a中呼叫執行緒b的join(),此時執行緒a就進入阻塞狀態,直到執行緒b完全執行完以後,執行緒a才結束阻塞狀態。
⑧ stop():已過時。當執行此方法時,強制結束當前執行緒。
⑨ sleep(long millitime):讓當前執行緒“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前執行緒是阻塞狀態。
⑩isAlive():判斷當前執行緒是否存活
4.1、執行緒優先順序涉及到的方法
4.1.1、表示優先順序的常量
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -->預設優先順序
4.1.2、如何獲取和設定當前執行緒的優先順序
① getPriority():獲取執行緒的優先順序
② setPriority(int p):設定執行緒的優先順序
4.1.3、說明
高優先順序的執行緒要搶佔低優先順序執行緒cpu的執行權。但是隻是從概率上講,高優先順序的執行緒高概率的情況下被執行。並不意味著只有當高優先順序的執行緒執行完以後,低優先順序的執行緒才執行。
4.2、程式碼
package com.zixue.java; class HelloThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ // try { // sleep(10); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i); } // if(i % 20 == 0){ // yield(); // } } } public HelloThread(String name){ super(name); } } public class ThreadMethodTest { public static void main(String[] args) { HelloThread h1 = new HelloThread("Thread:1"); // h1.setName("執行緒一"); //設定分執行緒的優先順序 h1.setPriority(Thread.MAX_PRIORITY); h1.start(); //給主執行緒命名 Thread.currentThread().setName("主執行緒"); Thread.currentThread().setPriority(Thread.MIN_PRIORITY); for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i); } // if(i == 20){ // try { // h1.join(); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } } // System.out.println(h1.isAlive()); } }