多線程
1、基礎概念
1.1 多線程技術
- 從軟件或者硬件上實現同時執行多個任務
- 具有多線程能攔的計算機因有硬件支持而能夠在同一時間執行多個線程
- 多線程編程常常也將其稱之為並發編程
1.2 並發和並行
1.3 進程和線程
- 進程:是正在運行的軟件,且一個進程最少包括一個線程
- 獨立性:進程是一個可以獨立運行的基本單位,也是操作系統調度的最小單元,同時也是系統分配資源和調度的獨立單位
- 動態性:進程的實質是程序的一次執行過程,進程是動態產生,動態消亡的
- 並發性:任何進程都可以同其他進程一起並發執行
- 線程:是進程中的單個順序控制流,是一條執行路徑
- 單線程:一個進程如果只有一條執行路徑,則稱為單線程程序
- 多線程:一個程序如果有多條執行路徑,則稱為多線程程序
- 二者關系
- 線程是進程中的單個順序控制流,是依賴於進程的,而一個進程最少包括一個線程,進程可以存在很多任務,每一個任務就是一個線程,這些執行路徑之間沒有任何的關聯關系
2、線程的實現
2.1 繼承Thread類
| 方法名 |
說明 |
| void run() |
在線程開啟后,此方法將被調用執行 |
| void start() |
使此線程開始執行,Java虛擬機會調用run方法() |
- 實現步驟
- 定義一個類繼承自Thread類,這里我們定義這個類為MyThread
- 重寫Thread類中的run方法(run方法中重寫的就是要被線程所執行的代碼)
- 創建MyThread類的對象
- 啟動線程(調用start方法)
- 注意:多線程的執行,具有隨機性
- 代碼實現
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();
- 兩個問題
- 為什么重寫run方法
- run方法和start方法的區別
- run():封裝線程執行的代碼,直接調用,相當於普通方法的調用
- start():啟動線程;然后由JVM調用此線程的run()方法
- start方法只能調用一次,如果調用多次程序將報錯
2.2 實現Runnable接口
| 方法名 |
說明 |
| Thread(Runnable target) |
分配一個新的Thread對象 |
| Thread(Runnable target, String name) |
分配一個新的Thread對象 |
- 實現步驟
- 定義一個類實現Runnable接口,這里我們定義為MyRunnable
- 在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) {
2.3 實現Callable類
| 方法名 |
說明 |
| V call() |
計算結果,如果無法計算結果,則拋出一個異常 |
| FutureTask(Callable callable) |
創建一個 FutureTask,一旦運行就執行給定的 Callable |
| V get() |
如有必要,等待計算完成,然后獲取其結果 |
- 實現步驟
- 定義一個類實現Callable接口,這里我們將這個類定義為MyCallable
- 在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); }
2.4 三種實現方式的區別
- 站在返回值的角度
- 繼承Thread類和實現Runnable接口的方式沒有返回值,獲取不到線程執行的結果
- 實現Callable接口的方式有返回值,所以可以獲取線程執行的結果
- 站在繼承方式的角度
- 繼承Thread類的方式,編程比較簡單,可以直接使用Thread類的方法,但同時它的擴展性相對較弱
- 實現Runnable或Callable接口,因為在Java中一個類可以實現多個接口,因此擴展性較高,但同時編程相對復雜,不能直接使用Thread類中的方法
3、Thread類的API
| 方法名 |
說明 |
| void setName(String name) |
設置線程名稱 |
| public final 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();
3.2 線程休眠
| 方法名 |
說明 |
| static native 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 {
3.3 線程優先級
3.3.1 線程調度
- 兩種調度方式
- 分時調度模型
- 所有線程輪流使用CPU的使用權,平均分配每個線程占用CPU的時間片
- 搶占式調度模型
- 優先級高的線程優先使用CPU,如果優先級相同,那么隨機選擇一個,優先級高的線程獲取的CPU時間片相對多一些
- JAVA使用的是搶占式調度模型
- 隨機性
- 假如計算機只有一個CPU ,那么某一時刻只能執行一條指令,線程只有得到CPU時間片,也就是使用權,才可以執行指令。所以說多線程程序的執行是隨機性的,因為誰搶到CPU的使用權是不一定的
3.3.2 相關方法
| 方法名 |
說明 |
| 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) {
3.4 守護線程
- Java語言中的線程可以分為普通線程和守護線程
- 守護線程的作用
- 為普通線程服務,如果普通線程結束,守護線程也會結束
- JVM會檢查線程的類型,如果當前的JVM進程中所有的線程都是守護線程,Jvm停止運行。
- 相關方法
| 方法名 |
說明 |
| void setDaemon(boolean on) |
將此線程標記為守護線程 |
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("備胎");
4、線程安全問題
4.1 數據安全問題
- 安全問題出現的條件
- 如何解決多線程安全問題
- 基本思想
- 如何實現
- 把多條語句操作共享數據的代碼鎖起來,讓任意時刻只能有一個線程執行即可
- 鎖在Java中存在兩種
- 內部鎖的實現
- 顯式鎖
4.2 同步代碼塊
- 格式
- synchronized(任意對象) { 多條語句操作共享數據的代碼 }
- synchronized(任意對象)
- 同步的好處和弊端
- 好處
- 弊端
- 當線程很多時,因為每個線程都會判斷同步上的鎖,這會很耗費資源,降低程序運行效率
- 代碼實現
public class SellTicket implements Runnable { private int tickets = 100; private Object obj = new Object(); @Override public void run() { while (true) { synchronized (obj) {
4.3 同步方法
- 同步方法
- 格式
- 修飾符 synchronized 返回值類型 方法名(方法參數) { 方法體;}
- 同步方法的鎖對象
- 同步靜態方法
- 修飾符 static synchronized 返回值類型 方法名(方法參數) { 方法體;}
- 同步靜態方法的鎖對象
- 代碼實現
public class Demo { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); t1.setName("窗口一"); t2.setName("窗口二"); t1.start(); t2.start(); } } class MyRunnable implements Runnable { private static int ticketCount = 100; @Override public void run() { while(true){ if("窗口一".equals(Thread.currentThread().getName())){