[Javaコンカレントプログラミング]スレッドプール関連のナレッジポイントの仕上げ

スレッドプールを使用する理由

プーリング技術:毎回リソースの消費を減らし、リソースの利用を改善します。

スレッドプールは、リソース(タスクの実行を含む)を制限および管理する方法を提供します。スレッドプールは、完了したタスクの数など、いくつかの基本的な統計も保持しています

スレッドプールを使用する利点:

  • リソース消費を減らします作成されたスレッドを再利用することで、スレッドの作成と破棄による消費を削減します。
  • 応答速度を改善します。タスクが到着すると、スレッドが作成されるまで待つことなく、タスクをすぐに実行できます。
  • スレッドの管理性を向上させます。スレッドは希少なリソースです。スレッドを無制限に作成すると、システムリソースを消費するだけでなく、システムの安定性も低下します。スレッドプールは、均一な割り当て、調整、監視に使用できます。

推奨閲覧:ポータル

スレッドプールの実現原理?

メソッドのソースコードを実行する

    public void execute(Runnable command) {
        // 如果任务为null,则抛出异常。        if (command == null)
            throw new NullPointerException();        // ctl 中保存的线程池当前的一些状态信息  AtomicInteger        int c = ctl.get();        //判断当前线程池中执行的任务数量是否小于corePoolSize        if (workerCountOf(c) < corePoolSize) {
            //如果小于,则通过addWorker新建一个线程,然后,启动该线程从而执行任务。            if (addWorker(command, true))
                return;
            c = ctl.get();        }        //通过 isRunning 方法判断线程池状态        //线程池处于 RUNNING 状态才会被并且队列可以加入任务        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();            // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务。            // 并尝试判断线程是否全部执行完毕。同时执行拒绝策略。            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 如果当前线程池为空就新创建一个线程并执行。            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }        //通过addWorker新建一个线程,并将任务(command)添加到该线程中;
        //然后,启动该线程从而执行任务。        //如果addWorker执行失败,则通过reject()执行相应的拒绝策略的内容。        else if (!addWorker(command, false))
            reject(command);
    }

スレッドプールは、スレッドを作成するときに、そのスレッドをワーカースレッドにカプセル化します。ワーカーがタスクを完了すると、実行のためにワークキューから周期的にタスクを取得します。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();        Runnable task = w.firstTask;        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {//循环执行任务
                w.lock();                //如果线程池正在停止,确保线程被中断
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&                      runStateAtLeast(ctl.get(), STOP))) &&                    !wt.isInterrupted())                    wt.interrupt();                try {
                    beforeExecute(wt, task);                    Throwable thrown = null;
                    try {
                        task.run();                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);                    }                } finally {
                    task = null;
                    w.completedTasks++;                    w.unlock();                }            }            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);        }    }

 

「Java並行プログラミング」スレッドプール関連のナレッジポイント

 

  1. スレッドプールは、コアスレッドプール [corePoolSize]内のスレッドがすべてタスクを実行しているかどうかを判断しますそうでない場合は、新しいワーカースレッドを作成してタスクを実行します。コアスレッドプール内のスレッドがすべてタスクを実行している場合は、次のプロセスに入ります。
  2. スレッドプールは、ワークキュー [BlockingQueue]がいっぱいかどうかを判断しますワークキューがいっぱいでない場合、新しく送信されたタスクはこのワークキューに格納されます。ワークキューがいっぱいの場合は、次のプロセスに入ります。
  3. スレッドプールは、スレッドプール [maximumPoolSize]のスレッドがすべて機能しているかどうかを判断しますそうでない場合は、タスクを実行するための新しいワーカースレッドを作成します。それがいっぱいの場合は、飽和戦略 [RejectedExecutionHandler.rejectedExecution()]に引き渡して、このタスクを処理します。

 

「Java並行プログラミング」スレッドプール関連のナレッジポイント

 

スレッドプールの使用方法

ThreadPoolExecutorの重要な分析

建設方法の重要なパラメーター

ThreadPoolExecutorメソッドには多くの構築パラメーターがあり、最も長いパラメーターを確認できます。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler) {        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize:コアスレッドの数は、同時に実行できるスレッド最小数を定義します
  • maximumPoolSize:キューに格納されているタスクがキューの容量達すると、現在同時に実行できるスレッドの数が最大スレッド数になります。[無制限のキューを使用する場合、このパラメーターは効果がありません]
  • workQueue:新しいタスクが来ると、最初に、現在実行中のスレッドの数がコアスレッドの数に達しているかどうかを判断します。コアスレッドの数に達した場合、新しいタスクはキューに格納されます
  • keepAliveTime:スレッドプール内のスレッド数がcorePoolSizeよりも大きい場合、この時点で新しいタスクが送信されない場合、コアスレッド外のスレッドはすぐには破棄されませんが、待ち時間がkeepAliveTimeを超えるまで待機してからリサイクルおよび破棄されます。 。
  • unit:keepAliveTimeの時間単位。
  • threadFactory:スレッドを作成するためのファクトリを設定するために使用されます。スレッドファクトリを介して、作成された各スレッドにわかりやすい名前を設定できます。
  • ハンドラー:飽和戦略現在同時に実行されているスレッドの数が最大スレッド数[maximumPoolSize]に達し、キューがいっぱいになると、飽和戦略が実行されます。

スレッドプールの簡単な使用

public class ThreadPoolTest {
    private static final int CORE_POOL_SIZE = 5; //核心线程数
    private static final int MAX_POOL_SIZE = 10; //最大线程数
    private static final int QUEUE_CAPACITY = 100; //任务队列的容量
    private static final Long KEEP_ALIVE_TIME = 1L; //等待时间
    public static void main(String[] args) {        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(                CORE_POOL_SIZE,                MAX_POOL_SIZE,                KEEP_ALIVE_TIME,                TimeUnit.SECONDS,                new ArrayBlockingQueue<>(QUEUE_CAPACITY),                new ThreadPoolExecutor.AbortPolicy());        for(int i = 0; i < 10 ; i ++){
            Runnable worker = new MyRunnable(""+ i); //创建任务
            threadPool.execute(worker); //通过execute提交
        }        threadPool.shutdown();        while(!threadPool.isTerminated()){
        }        System.out.println("Finished all threads");
    }}class MyRunnable implements Runnable {    private String command;    MyRunnable(String s) {
        this.command = s;
    }    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
        processCommand();        System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
    }    private void processCommand() {        try {            Thread.sleep(5000);
        } catch (InterruptedException e) {            e.printStackTrace();        }    }    @Override    public String toString() {        return this.command;
    }}

タスクキューとは何ですか?

  • ArrayBlockingQueue:配列構造に基づく制限付きブロッキングキュー。FIFO原則に従って要素をソートします。
  • LinkedBlockingQueue:リンクリスト構造に基づくブロッキングキュー、要素をFIFOで並べ替え、スループットは通常ArrayBlockingQueueよりも高く、Executors.newFixedThreadPool()はこのキューを使用します。
  • SynchronousQueue:要素を格納しないブロッキングキュー。各挿入操作は、別のスレッドが削除操作を呼び出すまで待機する必要があります。そうでない場合、挿入操作はブロックされ、スループットは通常LinkedBlockingQueueより高くなります。このキューは、Executors.newCachedThreadPool()によって使用されます。
  • PriorityBlockingQueue:優先度付きの無限ブロッキングキュー。

飽和戦略とは何ですか?

  • ThreadPoolExecutor.AbortPolicy:RejectedExecutionExceptionをスローして、新しいタスクの処理を拒否します。[デフォルトの飽和戦略]
  • ThreadPoolExecutor.CallerRunsPolicy [スケーラブルキューを提供する]:独自のスレッドを実行してタスクを実行する、つまり、rejectメソッドを、executeメソッドを呼び出すスレッドで直接実行します。実行プログラムが閉じている場合、タスクは破棄されます。したがって、この戦略は新しいタスクの送信速度を低下させ、プログラムの全体的なパフォーマンスに影響を与えます。アプリケーションがこの遅延を許容でき、タスク要求を実行する必要がある場合は、この戦略を選択できます。public void rejectedExecution(Runnable r、ThreadPoolExecutor e){if(!e.isShutdown()){r.run();}}
  • ThreadPoolExecutor.DiscardPolicy:  新しいタスクを処理せず、単に破棄します。
  • ThreadPoolExecutor.DiscardOldestPolicy:  このポリシーは、最も古い未処理のタスク要求を破棄し、現在のタスクを実行します。public void rejectedExecution(Runnable r、ThreadPoolExecutor e){if(!e.isShutdown()){e.getQueue()。poll(); e.execute(r);}}

もちろん、必要に応じて拒否戦略をカスタマイズすることもでき、RejectedExecutionHandlerを実装する必要があります。

スレッドプールを作成するには?

1. ThreadPoolExecutorのさまざまな構築メソッドを使用します。

2. 3種類のThreadPoolExecutorは、ExecutorフレームワークのツールクラスであるExecutorsを使用して作成できます。

「Alibaba Java Development Manual」の強制スレッドプールは、Executorsを使用してそれを作成することはできませんが、ThreadPoolExecutorメソッドを使用します。この処理方法により、生徒はスレッドプールの操作規則をより明確に記述し、リソースが枯渇するリスクを回避できます。

スレッドプールオブジェクトを返すエグゼキューターの欠点は次のとおりです  。FixedThreadPoolおよびSingleThreadExecutor:要求に許可されるキューの長さはInteger.MAX_VALUEであり、大量の要求が蓄積され、OOMにつながる可能性があります。CachedThreadPoolおよびScheduledThreadPool  :作成を許可されているスレッドの数はInteger.MAX_VALUEです。これにより、多数のスレッドが作成され、OOMが発生する可能性があります。

executeメソッドとsubmitメソッドの違いは何ですか?

  • execute()メソッドは、戻り値を必要としないタスクを送信するために使用されるため、タスクがスレッドプールによって正常に実行されたかどうかを判断することはできません。
threadPool.execute(new Runnable() {
    @Override
    public void run() {
    }}); //通过execute提交
  • submit()メソッドは、戻り値を必要とするタスクを送信するために使用されます。スレッドプールはFutureタイプのオブジェクトを返します。このFutureオブジェクトは、タスクが正常実行されたかどうかを判断するために使用できます。戻り値は、Futureのget()メソッドを通じて取得できます。get()メソッドは、タスクが完了するまで現在のスレッドをブロックし、getを使用します。 (長いタイムアウト、TimeUnit単位)メソッドは、現在のスレッドを一定期間ブロックし、すぐに戻ります。この時点では、タスクが完了していない可能性があります。
Future<Object> future = threadPool.submit(hasReturnValueTask);
try{
    Object s = future.get();
}catch(InterruptedException e){
    //处理中断异常
}catch(ExecutionException e){
    //处理无法执行任务异常
}finally{
    threadPool.shutdown();
}

この記事があなたに役立つと思うなら、あなたはそれを好きでそれをサポートするためにそれをフォローすることができます、またはあなたは私のパブリックアカウントをフォローすることができます。

 

おすすめ

転載: blog.csdn.net/weixin_50205273/article/details/108682056
おすすめ