アクシデントサマリーのハイライト-メインスレッドプールと子スレッドプールによるスレッドプールの共有によって引き起こされたアクシデント8(週に1回更新)

【問題点の説明】

  • オンライン製品の写真は空白の写真です。

【勢力圏】

  • APPホームページ、ストアリストページ、単一製品ページの詳細

【事故レベル】

  • P0

【加工工程】

  • 11:23アプリ製品の画像の多くが空であるというフィードバック

  • 11:30ポジショニングのバックグラウンドインターフェイスキャッシュデータは空を返します。

  • 11:35インターフェイスは、ダウングレードクエリデータベーススイッチをオンにし、製品イメージをキャッシュに完成させます

  • 11:36問題解決

  • 12:30コードを確認し、問題を見つけます

【問題の原因】

  • 画像サイズ変更タイプにより商品データを再更新します。更新処理では、マルチスレッド並列処理を有効にして時間を短縮します。処理中は、マーチャントの店舗数に応じてスレッドを開き、ストアの下のskuイメージは、サブスレッド処理が50のバッチで有効になっていることに従って更新されます。
  • メインスレッドとサブスレッドはすべて同じスレッドプールから発生し、スレッドプールの分離はありません。
  • メインスレッドと子スレッドが占有されると、お互いが待機します。
  • タスクの増加に伴い、新しく生成されたサブスレッドは徐々に最大スレッド数に達し、待機キューに入ります。
  • タスクの待機キューのバックログは徐々に最大値に達します
  • スレッド戦略を取ります。ここでは拒否戦略が使用されます。つまり、タスクは失われます。
  • 画像の更新タスクが失われ始め、オンラインキャッシュデータが失われました

【まとめ】

まず、スレッドプールの基本を確認しましょう。

スレッドプールの重要なパラメータ:

  1. corePoolSizeコアスレッド数のサイズ。スレッド数<corePoolSizeの場合、実行可能なスレッドが作成されます。
  2. maximumPoolSizeスレッドの最大数。スレッド数>=corePoolSizeの場合、ランナブルはworkQueueに入れられます。
  3. keepAliveTimeは、スレッドの数がcorePoolSizeのアイドル状態のスレッドが保持できる最大時間よりも大きい場合に、存続時間を保持します。
  4. 単位時間単位
  5. workQueueは、タスクのブロックキューを保持します
  6. threadFactoryはスレッドのファクトリを作成します
  7. ハンドラー拒否ポリシー

任务执行顺序:

  1. 当线程数小于corePoolSize时,创建线程执行任务。
  2. 当线程数大于等于corePoolSize并且workQueue没有满时,放入workQueue中
  3. 线程数大于等于corePoolSize并且当workQueue满时,新任务新建线程运行,线程总数要小于maximumPoolSize
  4. 当线程总数等于maximumPoolSize并且workQueue满了的时候执行handler的rejectedExecution。也就是拒绝策略。

ThreadPoolExecutor默认有四个拒绝策略:

  1. ThreadPoolExecutor.AbortPolicy() 直接抛出异常RejectedExecutionException
  2. ThreadPoolExecutor.CallerRunsPolicy() 直接调用run方法并且阻塞执行
  3. ThreadPoolExecutor.DiscardPolicy() 直接丢弃后来的任务
  4. ThreadPoolExecutor.DiscardOldestPolicy() 丢弃在队列中队首的任务

接下来我们通过一个demo来复现事故的原因过程

public class FuatureTaskDemo2 {
    private static ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(
            4,
            4,
            10L,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(2),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.DiscardPolicy());

    /**
     * @return
     */
    public void getWorker(String name) throws Exception {
        System.out.println("执行"+name+"程任务开始");
        for(int i=0;i<10;i++){
            int finalI = i;
            mExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("执行"+name+"中子线程:"+ finalI);
                }
            });
        }
        int getActiveCount = (mExecutor).getActiveCount();
        int getCorePoolSize = (mExecutor).getCorePoolSize();
        int getMaximumPoolSize = (mExecutor).getMaximumPoolSize();
        long getTaskCount = (mExecutor).getTaskCount();
        BlockingQueue<Runnable> blockingQueue = (mExecutor).getQueue();
        System.out.println("getActiveCount"+getActiveCount);
        System.out.println("getCorePoolSize"+getCorePoolSize);
        System.out.println("getMaximumPoolSize"+getMaximumPoolSize);
        System.out.println("getTaskCount"+getTaskCount);
        System.out.println("blockingQueue"+blockingQueue.size());
        mExecutor.shutdown();
    }


    public static void main(String[] args) {
        FuatureTaskDemo2 it = new FuatureTaskDemo2();
        FuatureTaskDemo2 it3 = new FuatureTaskDemo2();
        try {
            it3.getWorker("父线程");
            it.getWorker("子线程");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
复制代码

执行结果:

image.png

给我们的启示:

  • 由上述demo可以看到。30个子线程任务 最后只执行了 6个,其余的全部被拒绝。
  • 我们在执行核心数据线程的时候,尽量做到主-子线程池分离
  • 核心任务 拒绝策略一定是ThreadPoolExecutor.CallerRunsPolicy() 直接调用run方法并且阻塞执行,或者是 ThreadPoolExecutor.AbortPolicy() 直接抛出异常后进行重试。

おすすめ

転載: juejin.im/post/7079774983987626020