最詳細的java多線程教程來了

来源:https://www.cnblogs.com/coderjava/archive/2020/06/08/13068889.html
-Advertisement-
Play Games

1. 線程概述 1.1 線程和進程 進程是處於運行過程中的程式,並且具有一定的獨立功能 併發性:同一個時刻只能有一條指令執行,但多個進程指令被快速輪換執行 並行:多條指令在多個處理器上同時執行 線程是進程的執行單元 1.2 多線程的優勢 進程之間不能共用記憶體,但線程之間非常容易 系統創建進程時需要為 ...


1. 線程概述

1.1 線程和進程

  • 進程是處於運行過程中的程式,並且具有一定的獨立功能

  • 併發性:同一個時刻只能有一條指令執行,但多個進程指令被快速輪換執行

  • 並行:多條指令在多個處理器上同時執行

  • 線程是進程的執行單元

1.2 多線程的優勢

  • 進程之間不能共用記憶體,但線程之間非常容易

  • 系統創建進程時需要為該進程重新分配系統資源,但創建線程則代價小得多,因此使用多線程效率更高

  • Java語言內置了多線程功能

2. 線程創建與啟動

2.1 繼承Thread

 

 public class FirstThread extends Thread {
     private int i;
 ​
     @Override
     public void run() {
         for(i = 0; i < 50; i ++){
             System.out.println(this.getName() + "" + i);
         }
     }
 ​
     public static void main(String[] args){
         FirstThread ft = new FirstThread();
         for(int i =0; i < 100;i ++){
             System.out.println(Thread.currentThread().getName() + "" + i);
             if(i == 20) {
                 ft.run();
             }
         }
     }
 }

 

2.2 實現Runnable介面

 

 
public class FirstThread implements java.lang.Runnable {
     private int i;
 ​
     public void run() {
         for(i = 0; i < 50; i ++){
             System.out.println(Thread.currentThread().getName()+ "" + i);
         }
     }
 ​
     public static void main(String[] args){
         FirstThread ft = new FirstThread();
         for(int i =0; i < 100;i ++){
             System.out.println(Thread.currentThread().getName() + "" + i);
             if(i == 20) {
                 ft.run();
             }
         }
     }
 }

 

2.3 使用Callable和Future

  • Callable介面提供了一個call()方法可以作為線程執行體,call()方法有返回值且可以聲明拋出異常

  • Java5提供了Future介面來代表Callable介面里call()方法的返回值,併為Future介面提供了一個FutureTask實現類

  • Future介面定義的方法:

方法名作用
boolean cancel(boolean mayInterruptIfRunning) 試圖取消該Future里關聯的Callable任務
V get() 返回Callable任務里call方法的返回值,該方法會造成線程阻塞,等子線程執行完才能獲得
V get(long timeout, TimeUnit unit) 返回Callable任務里call方法的返回值。該方法讓程式最多阻塞timeoutunit指定的時間,如果經過指定時間Callable任務還沒有返回值則拋出TimeoutException異常
boolean isCancelled() Callable中的任務是否取消
boolean isDone() Callable中的任務是否完成

 

 public class CallableDemo {
     public static void main(String[] args){
         FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
             int i = 0;
             for( ; i < 100; i++){
                 System.out.println(i);
             }
             return i;
         });
         new Thread(task).start();
         try {
             System.out.println(task.get());
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
     }
 }

 

2.4 創建線程的三種方式對比

RunnableCallable優劣勢:

  • 線程類只是實現了RunnableCallable介面,還可以繼承其他類

  • Runnable和Callable情況下,多個線程可以共用同一個target對象,所以非常適合多個相同線程來處理同一份資源的情況

  • 編程稍稍複雜,如果需要訪問當前線程,則必須使用Thread.currentThread()

Thread優劣勢:

  • 線程類已經繼承了Thread類,所以不能再繼承其他父類

  • 編寫簡單,如果需要訪問當前線程,用this使用

3. 線程生命周期

3.1 新建和就緒狀態

  • new語句僅僅由Java虛擬機為其分配記憶體,並沒有表現出任何線程的動態特征

  • 如果直接調用繼承類的run方法,則只會有MainActivity,而且不能通過getName獲得當前執行線程的名字,而需用Thread.currentThread().getName()

  • 調用了run方法後,該線程已經不再處於新建狀態

3.2 運行和阻塞狀態

  • 當線程數大於處理器數時,存在多個線程在同一個CPU上輪換的現象

  • 協作式調度策略:只有當一個線程調用了sleep()yield()方法才會放棄所占用的資源——即必須線程主動放棄所占用的資源

  • 搶占式調度策略:系統給每個可執行的線程分配一個小的時間段來處理任務,當任務完成後,系統會剝奪該線程所占用的資源

  • 被阻塞的線程會在合適的時候重新進入就緒狀態

img

線程狀態轉換圖

3.3 死亡狀態

  • 測試線程死亡可用isAlive()

  • 處於死亡的線程無法再次運行,否則引發IllegalThreadStateException異常

 

“大清亡於閉關鎖國,學習技術需要交流和資料”。 在這裡我給大家準備了很多的學習資料免費獲取,包括但不限於java進階學習資料、技術乾貨、大廠面試題系列、技術動向、職業生涯等一切有關程式員的分享.java進階方法筆記,學習資料,面試題,電子書籍免費領取,讓你成為java大神,追到自己的女神,走向人生巔峰

4. 控制線程

4.1 join線程

  • MainActivity調用了A.join(),則MainActivity被阻塞,A線程執行完後MainActivity才執行

4.2 後臺線程(Daemon Thread)

  • 如果所有的前臺線程都死亡,後臺線程會自動死亡

 


img

運行結果

4.3 線程睡眠sleep

 

 
 try {
       Thread.sleep(200);
 } catch (InterruptedException e) {
        e.printStackTrace();
 }

 

  • sleep方法暫停當前線程後,會給其他線程執行機會,不會理會其他線程優先順序;但yield方法只會給優先順序相同或更高的線程

  • sleep方法將轉入阻塞狀態,直到經過阻塞時間才會轉入就緒;yield強制當前線程轉入就緒狀態

  • sleep方法拋出了InterruptedException,yield方法沒拋出異常

4.4 改變線程優先順序

  • 優先順序高的線程獲得較多的執行機會,優先順序低的線程獲得較少的執行機會

  • setPrioritygetPriority方法來設置和返回指定線程的優先順序

5. 線程同步

  • run()方法不具有同步安全性

  • *java*引入了同步監視器來解決多線程同步問題,sychronized(obj)obj就是共用資源

5.1 同步方法

  • 同步方法就是使用synchronized來修飾某個方法

  • 實例方法的同步監視器預設是this

  • *Java*中不可變類總是線程安全的,可變類對象需要額外的方法來保證其線程安全

 

 public class DaemonThread extends Thread {
 ​
     static int balance = 100;
     int drawAmount;
     String name;
 ​
     public DaemonThread(int drawAmount, String name){
         this.drawAmount = drawAmount;
         this.name = name;
     }
 ​
     @Override
     public void run() {
         this.draw(drawAmount);
     }
 ​
     public synchronized void draw(int amount){
         if(balance  >= amount){
             System.out.println(this.name + "取出了" + amount);
             try{
                 Thread.sleep(1);
             } catch (InterruptedException e){
                 e.printStackTrace();
             }
             balance -= amount;
             System.out.println("\t餘額為" + balance);
 ​
         } else{
             System.out.println(this.name + "取現失敗");
         }
     }
     public static void main(String[] args){
         new DaemonThread(50, "A").start();
         new DaemonThread(100, "B").start();
     }
 }

 

5.2 釋放同步監視器的鎖定

下列情況下,線程會釋放對同步監視器的鎖定

  • 當前線程的同步方法、同步代碼塊執行結束

  • 遇到了breakreturn

  • 遇到異常

  • 程式執行了同步監視器對象的wait()方法

下列情況下不會釋放:

  • 執行同步方法時,程式調用Thread.sleep() Thread.yield()方法

  • 其他線程調用了該線程的suspend方法

5.3 同步鎖

  • *Java5*開始,提供了一種功能更強大的同步鎖機制,可以通過顯式定義同步鎖對象來實現同步

  • Lock提供了比synchronized更廣泛的鎖定操作,並且支持多個相關的Condition對象

  • Lock類型: Lock ReadWriteLock ReentrantLock:常用,可以對一個加鎖的對象重新加鎖 ReentrantReadWriteLock StampedLock

方法名作用
lock 加鎖
unlock 解鎖

5.4 死鎖

A等B,B等A

5.5 線程通信

5.5.1 傳統的線程通信

方法名作用
wait 導致當前線程等待,直到其他線程調用該同步監視器的notify()notifyAll()方法
notify 喚醒在此同步監視器等待的單個線程
notifyAll 喚醒在此同步監視器等待的所有線程
  • wait()必須在加鎖的情況下執行

5.5.2 使用Condition

  • 如果系統中不適用synchronized來保證線程同步,而使用Lock對象來保證同步,那麼無法使用waitnotifynotifyAll()來進行線程通信

  • 當使用Lock對象,*Java*提供Condition保證線程協調

  • Condition方法如下

方法名作用
await 導致當前線程等待,直到其他線程調用該同步監視器的signal()signalAll()方法
signal 喚醒在此Lock對象的單個線程
signalAll 喚醒在此Lock對象的所有線程

5.5.3 使用阻塞隊列

  • *Java*提供了一個BlockingQueue介面

  • 當生產者線程試圖向BlockingQueue放入元素時,如果該隊列已滿,則該線程被阻塞;當消費者線程試圖從BlockingQueue取出元素時,如果該隊列已空,則該線程被阻塞

方法名作用
put(E e) 嘗試把E元素放入BlockingQueue
take() 嘗試從BlockingQueue的頭部取出元素

 

 
public class BlockingQueueThread extends Thread {
 ​
     private BlockingQueue<String> bq;
 ​
     public BlockingQueueThread(BlockingQueue<String> bq){
         this.bq = bq;
     }
 ​
     @Override
     public void run() {
         String[] strColl = new String[]{
                 "Java",
                 "Kotlin",
                 "JavaScript"
         };
 ​
 ​
 ​
         for(int i = 0; i < 1000; i ++){
             try {
                 System.out.println(getName() + "開始動工" + i);
                 bq.put(strColl[i % 3]);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
 ​
         System.out.println(getName() + "工作結束");
     }
 ​
     public static void main(String[] args){
         BlockingQueue<String> bq = new ArrayBlockingQueue<>(5);
         new BlockingQueueThread(bq).start();
     }
 }

 

img

結果展示

 

可以看到,當Thread-0運行到第6次時就已經被阻塞,不能往裡添加內容

 

“大清亡於閉關鎖國,學習技術需要交流和資料”。 在這裡我給大家準備了很多的學習資料免費獲取,包括但不限於java進階學習資料、技術乾貨、大廠面試題系列、技術動向、職業生涯等一切有關程式員的分享.java進階方法筆記,學習資料,面試題,電子書籍免費領取,讓你成為java大神,追到自己的女神,走向人生巔峰

6. 線程組和未處理的異常

  • ThreadGroup表示線程組,可以對一批線程進行分類管理

  • 子線程和創建它的父線程在同一個線程組內

  • ThreadGroup方法

方法名作用
int activeCount 返回線程組中活動線程的數目
interrupt 中斷此線程組中所有活動線程的數目
isDaemon 線程組是否是後臺線程組
setDaemon 設置後臺線程
setMaxPriority 設置線程組的最高優先順序

7. 線程池

  • 線程池在系統啟動時即創建大量空閑的線程

  • 程式將一個Runnable對象或Callable對象傳給線程池,線程池就會啟動一個空閑線程來執行他們

  • 線程結束不死亡,而是回到空閑狀態

  • *Java8*之後新增了一個Executors工廠類來生產線程池

7.1 ThreadPool

 

 public class ThreadPoolTest {
 ​
     public static void main(String[] args){
         ExecutorService pool = Executors.newFixedThreadPool(2);
 ​
         java.lang.Runnable target = () -> {
            for (int i = 0; i < 100 ; i ++){
                System.out.println(Thread.currentThread().getName() + "的i為" +i);
            }
         };
 ​
         pool.submit(target);
         pool.submit(target);
         pool.shutdown();
     }
 }

 

img

結果展示

7.2 ForkJoinPool

  • 將一個任務拆分成多個小任務並行計算,再把多個小任務的結果合併成總的計算結果

  • ForkJoinPoolExecutorService的實現類

 

 
public class PrintTask extends RecursiveAction {
 ​
     public static int THREADSH_HOLD = 50;
 ​
     private int start;
 ​
     private int end;
 ​
     public PrintTask(int start, int end){
         this.start = start;
         this.end = end;
     }
 ​
     @Override
     protected void compute() {
         if(end - start < THREADSH_HOLD){
             for(int i = start; i < end; i ++){
                 System.out.println(Thread.currentThread().getName() + "的i為" + i);
             }
         } else {
             PrintTask left = new PrintTask(start, (start + end) / 2);
             PrintTask right = new PrintTask((start + end) / 2 , end);
             left.fork();
             right.fork();
         }
     }
 ​
     public static void main(String[] args) throws InterruptedException {
         PrintTask printTask = new PrintTask(0 , 300);
         ForkJoinPool pool = new ForkJoinPool();
         pool.submit(printTask);
         pool.awaitTermination(2, TimeUnit.SECONDS);
         pool.shutdown();
 ​
     }
 }

 

img

 

“大清亡於閉關鎖國,學習技術需要交流和資料”。 在這裡我給大家準備了很多的學習資料免費獲取,包括但不限於java進階學習資料、技術乾貨、大廠面試題系列、技術動向、職業生涯等一切有關程式員的分享.java進階方法筆記,學習資料,面試題,電子書籍免費領取,讓你成為java大神,追到自己的女神,走向人生巔峰


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、問題描述 安裝在docker容器裡面的storage一直處於退出狀態,導致文件無法存儲。 2、解決方案 查看docker中安裝的容器 docker ps -a 嘗試啟動容器 docker start storage (或者 docker start "容器ID") 若嘗試啟動容器後,容器仍處於退 ...
  • 1 from datetime import * 2 today=datetime.today() 3 now=datetime.now() 4 5 #判斷今天是星期幾 6 today.isoweekday() 7 8 #計算一周以後是星期幾 9 day=timedelta(days=7) 10 t ...
  • 上一節給大家分享了掃雷游戲的源代碼,本篇文章當然也不會讓大家失望,專門針對C語言入門或者學習了部分知識之後的小伙伴來練手的游戲項目——《五子棋大戰》,本期並不是使用的easyX,因為考慮到有些大學的同學沒有接觸到這個,所以本期就是一個“黑漆漆”的控制台界面,這個就希望大家諒解哈!,話不多說,下麵我們 ...
  • 註意:一定要跟著博主的解說再看代碼的中文註釋及其下麵的一行代碼!!! 說到api版本控制,就是我們的前端人員請求的後臺介面可能有多個版本,後臺的介面地址一般是有兩種形式,博主現以這兩種形式逐一解釋api版本控制組件的源碼剖析。 第一種api版本控制的url格式一般是:http://localhost ...
  • 一、數組 數組也是一種引用類型,其父類是Object,使用“數據類型[]”聲明,如“int[] array”表示聲明瞭一個元素類型為int類型的數組array。 數組初始化語法: // 靜態初始化語法,即定義的時候就初始化好了所有的元素 int[] array1 = {100, 55, 30}; / ...
  • 22. 常用內置模塊 22.1 random模塊 隨機數據可以用於數學、測試、安全、演算法等領域中。內置random模塊,可用於生成偽隨機數。 真正意義上的隨機數或隨機事件是在某次產生過程中是按照實驗過程中表現的分佈概率隨機產生的,其結果是不可預測的,不可見的。而電腦中的隨機函數是按一定的演算法模擬產 ...
  • 所有知識體系文章,GitHub已收錄,歡迎Star!再次感謝,願你早日進入大廠! GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual 深入理解Java枚舉 一、什麼是枚舉 1.1 什麼是枚舉? 至於枚舉,我們先拿生活中的枚舉來入手,然 ...
  • 現在需要啟動一個selenium的爬蟲,使用火狐驅動+多線程,大家都明白的,現在電腦管家顯示CPU占用率20%,啟動selenium後不停的開啟瀏覽器+多線程, 好,沒過5分鐘,CPU占用率直接拉到90%+,電腦卡到飛起,定時程式雖然還在運行,但是已經類似於待機狀態, 很多人學習python,不知道 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...