JAvaマルチスレッド(6)スレッドプール


プールの概念はjavaでもよく見られ、接続プール、定数プールなどもあり、プールの役割も同様で、オブジェクト、資源の再利用に対して、システムのオーバーヘッドを減らし、運行効率を高める.
スレッドプールの主な機能:1.スレッドの作成と破棄回数を減らし、実行性能を向上させ、特に大量の非同期タスクの時2.スレッドをより合理的に管理することができる.例えば、スレッドの実行数を減らし、同じ時間に大量のタスクの実行を防止し、システムのクラッシュを招く
demo
まずdemoを挙げて、スレッドプールを使用する違いを見てみましょう.スレッドプール:
AtomicLong al = new AtomicLong(0l);
            Long s1 = System.currentTimeMillis();
            ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 100000, 100, TimeUnit.SECONDS, new LinkedBlockingDeque(20000));
            for(int i=0;i<20000;i++){
                pool.execute(new Runnable() {
                    
                    @Override
                    public void run() {
                        try {
                            al.incrementAndGet();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            pool.shutdown();
            pool.awaitTermination(1, TimeUnit.HOURS);
            System.out.println("  :"+(System.currentTimeMillis()-s1));
            System.out.println("AtomicLong="+al.get());

結果:
  :224
AtomicLong=20000

スレッド以外のプール:
AtomicLong al = new AtomicLong(0l);            
            Long s1 = System.currentTimeMillis();
            for(int i=0;i<20000;i++){
                Thread t = new Thread(new Runnable() {
                    
                    @Override
                    public void run() {
                        try {
                            al.incrementAndGet();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
                t.start();
            }
            while(true){
                if(al.get()==20000){
                    System.out.println("  :"+(System.currentTimeMillis()-s1));
                    System.out.println("AtomicLong="+al.get());
                    break;
                }
                Thread.sleep(1);
            }

結果:
  :2972
AtomicLong=20000

時間のかかる2つは13倍も差があり、差が大きいことから、非同期タスクが多数ある場合にスレッドプールを使用するとパフォーマンスが向上することがわかります.
スレッドプールの主なパラメータ
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

スレッドプールの主なパラメータは、次の6つです.
corePoolSize:コアプールのサイズで、コアプールのスレッドは回収されず、タスクがなければ空き状態になります.
maximumPoolSize:スレッドプールで最大許容スレッド数、
keepAliveTime:スレッド数がcorePoolSizeを超える場合、maximumPoolSize以下であれば「一時」スレッドが生成され、「一時」スレッドがタスクを実行してもすぐには回収されず、keepAliveTime時間内に新しいタスクがなければ回収されません.
unit:keepAliveTimeの単位.
workQueue:現在のスレッド数がcorePoolSizeを超えると、新しいタスクが待機状態になり、workQueueに存在します.
threadFactory:新しいスレッドを生成するファクトリ.
handler:スレッド数がworkQueueの上限を超えると、新しいスレッドが拒否されます.このパラメータは、新しいスレッドが拒否される方法です.
ここでcorePoolSizeとmaximumPoolSizeは相対的に理解しにくい.さらに詳しくは、1、プール内のスレッド数がcorePoolSizeより小さく、新しいタスクはキューに並ばずに新しいスレッドを直接追加する
2、プールのスレッド数がcorePoolSize以上で、workQueueが満たされていないので、新しいスレッドを追加するのではなく、新しいタスクをworkQueueに追加することをお勧めします.
3、プール内のスレッド数がcorePoolSize以上で、workQueueはすでにいっぱいであるが、スレッド数がmaximumPoolSizeより小さく、追加されたタスクを処理するために新しいスレッドを追加する
4、プール内のスレッド数がcorePoolSizeより大きく、workQueueがいっぱいで、スレッド数がmaximumPoolSize以上で、新しいタスクが拒否され、handlerを使用して拒否されたタスクを処理する
そのため、maximumPoolSize、corePoolSizeは設定しすぎてはいけません.そうしないと、メモリ、cpuの過負荷の問題が発生し、workQueueはできるだけ大きくすることができます.
Executors
ThreadPoolExecutorは使いやすいですが、jdkで設定されているいくつかのスレッドプール:Executors.newCachedThreadPool()(スレッド自動回収可能)、Executors.newFixedThreadPool(int)(固定サイズスレッドプール)、Executors.newSingleThreadExecutor()を使用することをお勧めします.ほとんどのシーンのニーズを満たしています.
1.newSingleThreadExecutorシングルスレッドプールの実装方法を見てください.
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }

corePoolSizeを1にし、workQueueサイズを無限大にするため、タスクを実行するスレッドは永遠に1つしかありません.新しいタスクはworkQueueで待っています.
2.newFixedThreadPool固定サイズスレッドプール
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }

newSingleThreadExecutorと似ていますが、単一スレッドから指定可能なスレッド数に変わり、workQueueは無限です.3.newCachedThreadPool
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

新CachedThreadPoolのすべての新しいタスクはすぐに実行され、corePoolSizeは0、maximumPoolSizeは整数最大値に設定され、すべてのスレッドが60秒を超えて使用されず、破棄されます.
workQueue
現在のスレッド数がcorePoolSizeを超えると、新しいタスクが待機状態になり、workQueueに存在します.workQueueの実現方式であるタスクの待機ポリシーは,有限キュー,無限キュー,直接コミットの3つに分類される.
前述の2つについて、jdkはArrayBlockingQueueではなくLinkedBlockingQueueを使用しています.有限キューの利点は、リソースと効率をよりカスタマイズした構成にありますが、無限キューの欠点と比較しても明らかです.
1).開発の難易度を高める.corePoolSize,maximumPoolSize,workQueue,3つのパラメータサイズを考慮すると,タスクを効率的に実行するとともに,タスクの過剰な問題を回避し,開発とテストの複雑さを大幅に向上させる必要がある.2).業務の突発を防止する.インターネットアプリケーションのトラフィックが急増していることも考慮しなければならない問題であり、無限キューを使用する場合、実行は遅くなる可能性がありますが、実行は保証されます.限られたキューを使用すると、タスクが拒否されるという問題が発生します.総合的に考えると、無限キューを使用することをお勧めします.特にマルチスレッドに慣れていない友达です.
ポリシーの拒否
拒否策というのは前にも言いましたが、任務が多すぎてmaximumPoolSizeを超えたらどうしますか?もちろん迎えられないし、迎えられないなら断るしかない.拒否するときは、拒否ポリシー、すなわちプロセッサを指定できます.
ポリシーを決定する親インタフェースはRejectedExecutionHandlerで、JDK自体はThreadPoolExecutorでユーザーに4つの拒否ポリシーを提供しています.見てみましょう.
1、AbortPolicy
JDKのデフォルトの拒否ポリシーであるRejectedExecutionExceptionを直接投げ出す
2、CallerRunsPolicy
拒否されたタスクを直接実行しようとします.スレッドプールが閉じられている場合、タスクは破棄されます.
3、DiscardOldestPolicy
最も遅い処理されていないタスクを削除し、拒否されたタスクを実行します.同様に、スレッドプールが閉じられている場合、タスクは破棄されます.
4、DiscardPolicy
任務をこっそり断る