多線程三(線程組和線程池)

来源:https://www.cnblogs.com/yumiaoxia/archive/2018/05/19/9058540.html
-Advertisement-
Play Games

線程組和線程池 一. 線程組 1. 線程組介紹及使用 Java使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許直接對線程組進行控制。對線程組的控制相當於控制這批線程。 在預設情況下,子線程和創建它的父線程同屬於一個線程組。 一旦線程假如某個線程組之後,該線程將一直屬 ...


線程組和線程池

一. 線程組

1. 線程組介紹及使用

Java使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許直接對線程組進行控制。對線程組的控制相當於控制這批線程。

在預設情況下,子線程和創建它的父線程同屬於一個線程組。

一旦線程假如某個線程組之後,該線程將一直屬於該線程組,知道該線程死亡,線程運行途中不能改變它所屬的線程組。

Thread提供了不同構造器設置新創建的線程屬於哪個線程組。提供getThreadGroup()方法返回該線程所屬的線程組對象。

ThreadGroup類提供瞭如下兩個構造器創建實例。

  • ThreadGroup(String name):以指定的線程組名字來創建新的線程組
  • ThreadGroup(ThreadGroup parent,String name):以指定的名字、指定的父線程組創建一個新線程組

Java程式不允許改線程組名字,通過getName()方法獲取線程組名字。

ThreadGroup類提供瞭如下常用的方法

  • int activeCount():返回此線程組中活動的線程數目
  • interrupt():中斷此線程組中的所有線程
  • isDaemon():判斷該線程組是否是後臺線程組
  • setDaemon(boolean daemon):把該線程組設置成後臺線程組。
  • setMaxPriority(int pri):設置線程組的最高優先順序。

2.線程組和異常處理機制

從Java 5開始,Java加強了線程的異常處理,如果線程執行過程中拋出了一個未處理異常,JVM在結束該線程之前會自動查找是否有對應的Thread.UncaughtExceptionHandler對象,如果找到該處理器對象,則會調用該對象的uncaughtException(Thread t,Throwable e)方法來處理該異常。

Thread類提供了兩個方法設置異常處理器。

  • static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):為該線程類的所有線程實例設置預設的異常處理器
  • setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):為指定的線程實例設置異常處理器

ThreadGroup類實現了Thread.UncaughtExceptionHandler介面,所以每個線程所屬的線程組將會作為預設的異常處理器。

如果線程執行過程中拋出了一個未處理異常,JVM在結束該線程之前會自動查找是否有對應的Thread.UncaughtExceptionHandler對象,如果找到該處理器對象,則會調用該對象的uncaughtException(Thread t,Throwable e)方法來處理該異常;否則,JVM會調用該線程所屬的線程組對象的uncaughtException()方法來處理該異常。

線程組處理異常的流程如下:

  • 如果該線程組有父線程組,則調父線程組的uncaughtException()方法來處理該異常。
  • 如果該線線程實例所屬的線程類有預設的異常處理器,那麼調用該異常處理器來處理異常
  • 如果該對象是ThreadDeath對象,則不做任何處理;否則,將異常跟蹤棧的信息列印到System.err錯誤輸出流,並結束該線程。

下麵主程式設置了異常處理器。

package com.gdut.thread;

class MyExHandler implements Thread.UncaughtExceptionHandler{
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println(t+"線程出現了異常"+e);
    }
}
public class ExHandler {
    public static void main(String[] args) {
        Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler());
        int a=5/0;
        System.out.println("程式正常結束");
    }
}

輸出:Thread[main,5,main]線程出現了異常java.lang.ArithmeticException: / by zero

二. 線程池

系統啟動一個新線程的成本是非常高的,因為它涉及與操作系統交互。當程式中需要創建大量生存期很短暫的線程時,應該考慮使用線程池來提高系統性能。

與資料庫連接池類似的是,線程池在系統啟動時即創建大量空閑的線程,當序將一個Runnable對象或Callable對象創給線程池,線程池就會啟動一個線程來執行他們的run()或call()方法,當run()或call()方法執行結束後,該線程並不會死亡,而是再次返回線程池中稱為空閑狀態,等待執行下一個Runnable對象的run()或call()方法。

除此之外,使用線程池可以有效地控制系統中併發線程的數量,當系統中包含大量併發線程時,會導致系統性能劇烈下降,甚至導致JVM崩潰。

2.1 Java 8改進的線程池

從Java 5開始,Java內建支持線程池。Java 5新增了一個Executors工廠類來生產線程池,包含如下靜態方法生產不同的線程池。

  • newCachedThreadPool():創建一個具有緩存功能的線程池,系統根據需要創建線程,這些線程將被還存線上程池中
  • newFixdThreadPool(int nThread):創建一個可重用的,具有固定線程數的線程池
  • newSingleThreadExecutor():創建一個只有單線程的線程池,相當於newFixdThreadPool(int nThread)傳入參數1。
  • newScheduledThreadPool(int corePoolSize):創建具有指定線程數的線程池,它可以在指定延遲後執行線程任務。corePoolSize指池中所保存的線程數,即使線程是空閑的也被保存線上程池內
  • newSingleThreadledExecutor():創建一個線程的線程池,它可以在指定延遲後執行線程任務。
  • ExecutorService newWorkStealingPool(int parallelism):創建持有足夠線程的線程池來支持給定的並行級別,該方法還會使用多個隊列來減少競爭。
  • ExecutorService newWorkStealingPool():該方法是前一個方法的簡化版,他根據當前機器的CPU核數設置並行級別。

上面7個方法中的前三個方法返回一個ExecutorService對象,該對象代表一個線程池,它可以執行Runnable對象和Callable對象所代表的線程,中間兩個方法返回一個ScheduledExecutorService線程池,它是ExecutorService的子類,它可以在指定延遲後執行線程任務,最後兩個方法是Java 8新增的,他充分利用多CPU的並行能力。這兩個方法生成的work stealing池,都相當於後臺線程池,如果所有的前臺線程都死亡了,work stealing池中的線程也會自動死亡。

ExecutorService對象的submit()方法可以執行Runnable對象或C對象代表的任務,返回Future對象,該對象代表call()方法的返回值。

ScheduledExecutorServic代表指定延遲後或周期性的執行任務的線程池,它提供瞭如下四個方法

  • ScheduledFuture<V> schedule(Callable<V> callble,long delay,TimeUnit unit):指定callable任務將在delay延遲後執行
  • ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit):指定command任務將在delay延遲後執行
  • ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit):指定command任務將在delay延遲後以設定頻率重覆執行,也就是說,在initialDelay後開始執行,依次在initialDelay+period、initialDelay+period*2...重覆執行。
  • ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit):創建並執行一個給定初始延遲後首次啟動的定期操作,以後每一次執行終止和下一次執行的開始之間都存在給定的延遲。如果任務在某一次執行時遇到異常,就會取消後續執行;否則,只能通過程式顯式取消或終止該任務。

用完一個線程池後,應該調用線程池的shutdown()方法啟動線程池的關閉序列,調用shutdown()方法後的線程池不再接受任務,但會將以前所有已提交任務執行完成;另外也可以調用shutdownNow()方法關閉線程池,該方法試圖停止所有在執行的活動任務,暫停處理正在等待執行的任務,並返回等待執行的任務列表。

使用線程池的任務步驟如下:

  1. 調用Executors類的靜態工廠方法創建一個ExecutorService對象,該對象代表一個線程池
  2. 創建Runnable實現類或Callable實現類的實例,作為線程執行任務
  3. 調用ExecutorService對象的submit()方法來提交Runnable或Callable的實例代表的任務
  4. 當不想提交任何任務時,調用ExecutorService對象的shutdown()方法來關閉線程池。

實例如下:

package com.gdut.thread.threadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(6);
        Runnable runnable1 = ()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName()+" "+i);
            }
        };
        Runnable runnable2 = ()->{
            for (char i = 'a'; i < 'z'; i++) {
                System.out.println(Thread.currentThread().getName()+" "+i);
            }
        };
        pool.submit(runnable1);
        pool.submit(runnable2);
        pool.submit(runnable2);
        pool.shutdown();
    }
}

 

2.2 Java 8 增強的線程池

為了充分利用多CPU的優勢、多核CPU的性能優勢。可以考多個小任務,把小任務放到多個處理器核心上並行執行;當多個小任務執行完成之後,再將這些執行結果合併起來即可。Java 7提供了ForkJoinPool來支持這個功能。

ForkJoinPool是ExecutorService的實現類,因此是一種特殊的線程池。提供瞭如下兩個常用的構造器

  • ForkJoinPool(int parallelism):創建一個包含parallelism個並行線程的ForkJoinPool.
  • ForkJoinPool():以Runtime.availableProssesors()方法的返回值作為paralelism參數來創建ForkJoinPool.

Java 8進一步拓展了ForkJoinPool的功能,Java 8增加了通用池功能。ForkJoinPool通過如下兩個方法提供通用池功能。

  • ForkJoinPool commonPool():該方法返回一個通用池,通用池的狀態不會受shutdown()或shutdownNow()方法的影響。
  • int getCommonPoolParallelism():該方法返回通用池的並行級別。

創建了通用池ForkJoinPool實例之後,就可調用ForkJoinPool的submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法來執行指定任務了。其中,ForkJoinTask代表一個並行,合併的任務。

ForkJoinTask是一個抽象類,它還有兩個抽象子類:RecursiveAction和recursiveTask。其中RecursiveAction代表沒有返回值的任務,RecursiveTask代表有返回值的任務。

下麵程式將一個大任務(列印0~500)的數值分成多個小任務,並將任務交給ForkJoinPool來執行。

package com.gdut.thread.threadPool;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

class PrintTask extends RecursiveAction{

    private static final int THRESHOLD = 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<THRESHOLD){
            for (int i = start; i <end ; i++) {
                System.out.println(Thread.currentThread().getName()+"的i值"+i);
            }
        }else{
            //當end與start的差大於THRESHOLD時,即要列印的數超過50時,將大任務分成兩個小任務
            int middle = (end+start)/2;
            PrintTask left = new PrintTask(start,middle);
            PrintTask right = new PrintTask(middle,end);
            left.fork();
            right.fork();
        }
    }
}
public class ForkJoinPoolTest{
    public static void main(String[] args) throws InterruptedException{
        ForkJoinPool pool = new ForkJoinPool();
        pool.submit(new PrintTask(0,500));
        pool.awaitTermination(2, TimeUnit.SECONDS);
        pool.shutdown();
    }

}

8核電腦的執行效果

下麵程式示範了使用RecursiveTask對一個長度為100的數組的元素值進行累加。

package com.gdut.thread.threadPool;

import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

class CalTask extends RecursiveTask<Integer>{

    private static final int THRESHOLD = 20;
    private int[] arr;
    private int start;
    private int end;

    public CalTask(int[] arr,int start,int end) {
        this.arr = arr;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int sum = 0;
        if(end-start<THRESHOLD){
            for (int i = start; i <end ; i++) {
                sum +=arr[i];
                System.out.println(Thread.currentThread().getName()+"加上"+arr[i]+"後的累加值為:"+sum);
            }
            return sum;
        }else{
            int middle = (end+start)/2;
            //將大任務分成兩個小任務
            CalTask left = new CalTask(arr,start,middle);
            CalTask right = new CalTask(arr,middle,end);
            //執行兩個小任務
            left.fork();
            right.fork();
            //將兩個小任務累加的結果合併起來
           return left.join() + right.join();
        }

    }
}

public class Sum {

    public static void main(String[] args) throws Exception{
        int[] arr = new int[100];
        Random rand = new Random();
        int total = 0;
        for (int i = 0; i <100 ; i++) {
            int tmp = rand.nextInt(20);
            total += (arr[i] = tmp);
        }
        System.out.println(total);

        ForkJoinPool pool = new ForkJoinPool();
        Future future = pool.submit(new CalTask(arr,0,100));
        System.out.println(future.get());
        pool.shutdown();
    }
}


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

-Advertisement-
Play Games
更多相關文章
  • 昨天群里有人問個問題:為什麼button加了文字後,產生了對齊不一致的問題。 原因在於baseline的對齊問題。 然後就有人推薦了一篇文章:關於Vertical-Align你需要知道的事情 其中裡面最後一個例子講到瞭如何解決inline元素換行的問題。 裡面說用註釋可以解決換行問題,我測試了下發現 ...
  • 整整弄了兩天,踩了無數的坑,各種奇怪的error,最後終於編譯成功了。 網上的教程基本上都過時了,或者是版本不對,都會報一些奇怪的錯誤,這裡總結一下目前可行的流程。 node版本:v10.1.0。 首先需要一些準備工作: 1、Visual Studio 2017 傳送門:https://www.vi ...
  • 每次做項目都要自己搭建項目目錄,或者換了公司就的重新搭建項目目錄,是不是很麻煩呢?有沒有想過一次性把項目目錄搭建好,以後直接用呢?你首先想到的可能是複製自己原來的項目,然後刪除、修改等等。然而有個更方便的方法,那就是用vue init一個本地或者Git上的目錄結構。下麵我將分別分享如何快速創建本地目 ...
  • 1、業務功能:四個按鈕只能選中一個,且預設選擇水準4 先看代碼。 <div class="btn-group" data-toggle="buttons"> <label class="btn btn-info"> <input type="radio" class="level_select" n ...
  • 傳統瀏覽器可以使用window.ActiveXObject檢查瀏覽器是否啟用相關的控制項。檢查瀏覽器是否啟用flash控制項,需要先檢查瀏覽器是否支持ActiveXObject,可以使用typeof檢查window.ActiveXObject是否等於undefined,語法: 如果,window.Act ...
  • 我對架構定義的理解 大概在7~8年前,我曾經有一個美國對口的架構師導師,他對我講架構其實是發現利益相關者(stakeholder),然後解決他們的關註點(concerns),後來我讀到一本書《軟體系統架構:使用視點和視角與利益相關者合作》,裡面提到的理念也是這樣說:系統架構的目標是解決利益相關者的關 ...
  • 註:這是一系列基於實驗樓網路培訓的python學習日記,內容零散,只是便於我自己回顧,有需要請瞭解www.shiyanlou.com。 1. Github相關 首先是複習github相關操作: 1.1 完成創建賬號和倉庫 登陸github.com,創建new repository,自動初始化READ ...
  • 實現的排序演算法 冒泡排序、選擇排序、快速排序 具體實現 選用mfc中的單文檔框架 ①SetTimer函數的用法。 ②使用畫筆畫直線。 ③使用FillSolidRect()函數覆蓋某一矩形區域內的內容;使用TextOutW()函數在某坐標位置輸出字元串。 效果截圖 生成隨機數 選擇一種排序演算法 加速減 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...