目次
基本
スレッド プール
限制队列中任务数
示例:并行执行独立的单线程任务
总结
ホームページ Java &#&チュートリアル ThreadPoolExecutor を使用して Java で独立したシングルスレッド タスクを並列実行する方法の詳細な紹介

ThreadPoolExecutor を使用して Java で独立したシングルスレッド タスクを並列実行する方法の詳細な紹介

Mar 23, 2017 am 11:06 AM

タスク実行フレームワークは Java SE 5.0 で導入されました。これは、マルチスレッド プログラミング開発の簡素化において大きな進歩です。このフレームワークを使用すると、タスクを簡単に管理できます。タスクの ライフ サイクル と実行戦略を管理します。

この記事では、簡単な例を使用して、このフレームワークによってもたらされる柔軟性とシンプルさを示します。

基本

実行フレームワークには、タスクの実行を管理するためのExecutorインターフェイスが導入されています。 Executor は、実行可能なタスクを送信するために使用されるインターフェイスです。このインターフェイスは、タスクの送信をタスクの実行から分離します。異なる実行戦略を持つエグゼキュータはすべて、同じ送信インターフェイスを実装します。実行戦略を変更しても、タスク送信ロジックには影響しません。

Runnable オブジェクトを実行のために送信する場合、それは非常に簡単です:

Executor exec = …;
exec.execute(runnable);
ログイン後にコピー

スレッド プール

前に述べたように、実行プログラムが送信された実行可能タスクをどのように実行するかは Executor インターフェイスでは指定されておらず、実行プログラムに依存します。特定のタイプを使用します。このフレームワークはいくつかの異なるエグゼキュータを提供し、実行戦略はシナリオごとに異なります。

使用できる最も一般的なエグゼキュータのタイプは、ThreadPoolExecutor クラス (およびそのサブクラス) のインスタンスであるスレッド プール エグゼキュータです。 ThreadPoolExecutor は、スレッド プールとワーク キューを管理します。スレッド プールには、タスクの実行に使用されるワーカー スレッドが格納されます。

他のテクノロジーにおける「プール」の概念は理解できたはずです。 「プール」を使用する最大の利点の 1 つは、リソースの作成コストを削減し、使用して解放した後、再利用できることです。もう 1 つの間接的な利点は、使用されるリソースの量を制御できることです。たとえば、システム リソースを損なうことなく、スレッド プールのサイズを調整して、必要な負荷を達成できます。

このフレームワークは、スレッド プールを作成するための Executor と呼ばれるファクトリ クラスを提供します。このエンジニアリング クラスを使用すると、さまざまな特性を持つスレッド プールを作成できます。多くの場合、基礎となる実装は同じ (ThreadPoolExecutor) ですが、ファクトリ クラスを使用すると、複雑な

コンストラクター を使用せずにスレッド プールを迅速にセットアップできます。エンジニアリング クラスのファクトリ メソッドは次のとおりです。

  • newFixedThreadPool: このメソッドは、最大容量が固定されたスレッド プールを返します。新しいスレッドはオンデマンドで作成され、スレッドの数は構成された数を超えません。スレッド数が最大値に達しても、スレッド プールは変更されません。

  • newCachedThreadPool: このメソッドは無制限のスレッド プールを返します。つまり、最大数の制限はありません。ただし、ワークロードが減少すると、このタイプのスレッド プールは未使用のスレッドを破棄します。

  • newSingleThreadedExecutor: このメソッドは、すべてのタスクが単一のスレッドで実行されることを保証できるエグゼキューターを返します。

  • newScheduledThreadPool: このメソッドは、遅延タスクおよびスケジュールされたタスクの実行をサポートする固定サイズのスレッド プールを返します。

これはほんの始まりにすぎません。この記事の範囲を超えた他にも Executor の使用方法がいくつかあります。以下について学習することを強くお勧めします:

  • ExecutorService インターフェースによって宣言されるライフサイクル管理メソッド (shutdown() や awaitTermination() など) )。

  • CompletionServiceを使用してタスクのステータスをクエリし、戻り値がある場合は戻り値を取得します。

ExecutorService インターフェイスは、スレッド プールをシャットダウンし、使用されなくなったリソースが確実にクリーンアップされるようにするメソッドを提供するため、特に重要です。幸いなことに、ExecutorService インターフェイスは非常にシンプルで一目瞭然なので、そのドキュメントを包括的に学習することをお勧めします。

大まかに言えば、shutdown() メッセージを ExecutorService に送信すると、新しく送信されたタスクは受信されませんが、キュー内に残っているタスクは引き続き処理されます。 isTerminated() を使用して ExecutorService の終了ステータスをクエリしたり、awaitTermination(...) メソッドを使用して ExecutorService が終了するのを待機したりできます。最大タイムアウトをパラメータとして渡すと、awaitTermination メソッドは永久に待機しません。

警告: JVM プロセスは決して終了しないという理解には、いくつかの誤りと混乱があります。 executorService を閉じずに基礎となるスレッドを破棄するだけの場合、JVM は終了しません。最後の通常のスレッド (非デーモン スレッド) が終了すると、JVM も終了します。

ThreadPoolExecutor の構成

Executor ファクトリ クラスを使用せず、手動で ThreadPoolExecutor を作成する場合は、コンストラクターを使用して作成および構成する必要があります。以下は、このクラスで最も広く使用されているコンストラクターの 1 つです:

public ThreadPoolExecutor(
    int corePoolSize,
    int maxPoolSize,
    long keepAlive,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    RejectedExecutionHandler handler);
ログイン後にコピー

ご覧のとおり、以下を設定できます:

  • コア プールのサイズ (スレッド プールが使用するサイズ)

  • 最大プールサイズ

  • 生存時間、アイドルスレッドはこの時間を過ぎると破棄されます

  • タスクを保存するワークキュー

  • タスクの送信が拒否された後に実行される戦略

限制队列中任务数

限制执行任务的并发数、限制线程池大小对应用程序以及程序执行结果的可预期性与稳定性有很大的好处。无尽地创建线程,最终会耗尽运行时资源。你的应用程序因此会产生严重的性能问题,甚至导致程序不稳定。

这只解决了部分问题:限制了并发任务数,但并没有限制提交到等待队列的任务数。如果任务提交的速率一直高于任务执行的速率,那么应用程序最终会出现资源短缺的状况。

解决方法是:

  • 为Executor提供一个存放待执行任务的阻塞队列。如果队列填满,以后提交的任务会被“拒绝”。

  • 当任务提交被拒绝时会触发RejectedExecutionHandler,这也是为什么这个类名中引用动词“rejected”。你可以实现自己的拒绝策略,或者使用框架内置的策略。

默认的拒绝策略可以让executor抛出一个RejectedExecutionException异常。然而,还有其他的内建策略:

  • 悄悄地丢弃一个任务

  • 丢弃最旧的任务,重新提交最新的

  • 在调用者的线程中执行被拒绝的任务

什么时候以及为什么我们才会这样配置线程池?让我们看一个例子。

示例:并行执行独立的单线程任务

最近,我被叫去解决一个很久以前的任务的问题,我的客户之前就运行过这个任务。大致来说,这个任务包含一个组件,这个组件监听目录树所产生的文件系统事件。每当一个事件被触发,必须处理一个文件。一个专门的单线程执行文件处理。说真的,根据任务的特点,即使我能把它并行化,我也不想那么做。一天的某些时候,事件到达率才很高,文件也没必要实时处理,在第二天之前处理完即可。

当前的实现采用了一些混合且匹配的技术,包括使用UNIX SHELL脚本扫描目录结构,并检测是否发生改变。实现完成后,我们采用了双核的执行环境。同样,事件的到达率相当低:目前为止,事件数以百万计,总共要处理1~2T字节的原始数据。

运行处理程序的主机是12核的机器:很好机会去并行化这些旧的单线程任务。基本上,我们有了食谱的所有原料,我们需要做的仅仅是把程序建立起来并调节。在写代码前,我们必须了解下程序的负载。我列一下我检测到的内容:

  • 有非常多的文件需要被周期性地扫描:每个目录包含1~2百万个文件

  • 扫描算法很快,可以并行化

  • 处理一个文件至少需要1s,甚至上升到2s或3s

  • 处理文件时,性能瓶颈主要是CPU

  • CPU利用率必须可调,根据一天时间的不同而使用不同的负载配置。

我需要这样一个线程池,它的大小在程序运行的时候通过负载配置来设置。我倾向于根据负载策略创建一个固定大小的线程池。由于线程的性能瓶颈在CPU,它的核心使用率是100%,不会等待其他资源,那么负载策略就很好计算了:用执行环境的CPU核心数乘以一个负载因子(保证计算的结果在峰值时至少有一个核心):

int cpus = Runtime.getRuntime().availableProcessors();
int maxThreads = cpus * scaleFactor;
maxThreads = (maxThreads > 0 ? maxThreads : 1);
ログイン後にコピー

然后我需要使用阻塞队列创建一个ThreadPoolExecutor,可以限制提交的任务数。为什么?是这样,扫描算法执行很快,很快就产生庞大数量需要处理的文件。数量有多庞大呢?很难预测,因为变动太大了。我不想让executor内部的队列不加选择地填满了要执行的任务实例(这些实例包含了庞大的文件描述符)。我宁愿在队列填满时,拒绝这些文件。

而且,我将使用ThreadPoolExecutor.CallerRunsPolicy作为拒绝策略。为什么?因为当队列已满时,线程池的线程忙于处理文件,我让提交任务的线程去执行它(被拒绝的任务)。这样,扫面会停止,转而去处理一个文件,处理结束后马上又会扫描目录。

下面是创建executor的代码:

ExecutorService executorService =
    new ThreadPoolExecutor(
        maxThreads, // core thread pool size
        maxThreads, // maximum thread pool size
        1, // time to wait before resizing pool
        TimeUnit.MINUTES, 
        new ArrayBlockingQueue<Runnable>(maxThreads, true),
        new ThreadPoolExecutor.CallerRunsPolicy());
ログイン後にコピー

下面是程序的框架(极其简化版):

// scanning loop: fake scanning
while (!dirsToProcess.isEmpty()) {
    File currentDir = dirsToProcess.pop();

    // listing children
    File[] children = currentDir.listFiles();

    // processing children
    for (final File currentFile : children) {
        // if it&#39;s a directory, defer processing
        if (currentFile.isDirectory()) {
            dirsToProcess.add(currentFile);
            continue;
        }

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    // if it&#39;s a file, process it
                    new ConvertTask(currentFile).perform();
                } catch (Exception ex) {
                    // error management logic
                }
            }
        });
    }
}

// ...
// wait for all of the executor threads to finish
executorService.shutdown();
try {
    if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
        // pool didn&#39;t terminate after the first try
        executorService.shutdownNow();
    }

    if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
        // pool didn&#39;t terminate after the second try
    }
} catch (InterruptedException ex) {
    executorService.shutdownNow();
    Thread.currentThread().interrupt();
}
ログイン後にコピー

总结

看到了吧,Java并发API非常简单易用,十分灵活,也很强大。真希望我多年前可以多花点功夫写一个这样简单的程序。这样我就可以在几小时内解决由传统单线程组件所引发的扩展性问题。

以上がThreadPoolExecutor を使用して Java で独立したシングルスレッド タスクを並列実行する方法の詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

PHP:Web開発の重要な言語 PHP:Web開発の重要な言語 Apr 13, 2025 am 12:08 AM

PHPは、サーバー側で広く使用されているスクリプト言語で、特にWeb開発に適しています。 1.PHPは、HTMLを埋め込み、HTTP要求と応答を処理し、さまざまなデータベースをサポートできます。 2.PHPは、ダイナミックWebコンテンツ、プロセスフォームデータ、アクセスデータベースなどを生成するために使用され、強力なコミュニティサポートとオープンソースリソースを備えています。 3。PHPは解釈された言語であり、実行プロセスには語彙分析、文法分析、編集、実行が含まれます。 4.PHPは、ユーザー登録システムなどの高度なアプリケーションについてMySQLと組み合わせることができます。 5。PHPをデバッグするときは、error_reporting()やvar_dump()などの関数を使用できます。 6. PHPコードを最適化して、キャッシュメカニズムを使用し、データベースクエリを最適化し、組み込み関数を使用します。 7

PHP対Python:違いを理解します PHP対Python:違いを理解します Apr 11, 2025 am 12:15 AM

PHP and Python each have their own advantages, and the choice should be based on project requirements. 1.PHPは、シンプルな構文と高い実行効率を備えたWeb開発に適しています。 2。Pythonは、簡潔な構文とリッチライブラリを備えたデータサイエンスと機械学習に適しています。

PHP対その他の言語:比較 PHP対その他の言語:比較 Apr 13, 2025 am 12:19 AM

PHPは、特に迅速な開発や動的なコンテンツの処理に適していますが、データサイエンスとエンタープライズレベルのアプリケーションには良くありません。 Pythonと比較して、PHPはWeb開発においてより多くの利点がありますが、データサイエンスの分野ではPythonほど良くありません。 Javaと比較して、PHPはエンタープライズレベルのアプリケーションでより悪化しますが、Web開発により柔軟性があります。 JavaScriptと比較して、PHPはバックエンド開発により簡潔ですが、フロントエンド開発のJavaScriptほど良くありません。

PHP対Python:コア機能と機能 PHP対Python:コア機能と機能 Apr 13, 2025 am 12:16 AM

PHPとPythonにはそれぞれ独自の利点があり、さまざまなシナリオに適しています。 1.PHPはWeb開発に適しており、組み込みのWebサーバーとRich Functionライブラリを提供します。 2。Pythonは、簡潔な構文と強力な標準ライブラリを備えたデータサイエンスと機械学習に適しています。選択するときは、プロジェクトの要件に基づいて決定する必要があります。

カプセルの量を見つけるためのJavaプログラム カプセルの量を見つけるためのJavaプログラム Feb 07, 2025 am 11:37 AM

カプセルは3次元の幾何学的図形で、両端にシリンダーと半球で構成されています。カプセルの体積は、シリンダーの体積と両端に半球の体積を追加することで計算できます。このチュートリアルでは、さまざまな方法を使用して、Javaの特定のカプセルの体積を計算する方法について説明します。 カプセルボリュームフォーミュラ カプセルボリュームの式は次のとおりです。 カプセル体積=円筒形の体積2つの半球体積 で、 R:半球の半径。 H:シリンダーの高さ(半球を除く)。 例1 入力 RADIUS = 5ユニット 高さ= 10単位 出力 ボリューム= 1570.8立方ユニット 説明する 式を使用してボリュームを計算します。 ボリューム=π×R2×H(4

PHPの影響:Web開発など PHPの影響:Web開発など Apr 18, 2025 am 12:10 AM

phphassiblasifly-impactedwebdevevermentandsbeyondit.1)itpowersmajorplatformslikewordpratsandexcelsindatabase interactions.2)php'sadaptableability allowsitale forlargeapplicationsusingframeworkslikelavel.3)

PHP:多くのウェブサイトの基礎 PHP:多くのウェブサイトの基礎 Apr 13, 2025 am 12:07 AM

PHPが多くのWebサイトよりも優先テクノロジースタックである理由には、その使いやすさ、強力なコミュニティサポート、広範な使用が含まれます。 1)初心者に適した学習と使用が簡単です。 2)巨大な開発者コミュニティと豊富なリソースを持っています。 3)WordPress、Drupal、その他のプラットフォームで広く使用されています。 4)Webサーバーとしっかりと統合して、開発の展開を簡素化します。

See all articles