スレッドプールの不適切な使用による弊害(1): ローカル変数スレッドプールとクラス変数スレッドプールの使い方

序文:

  スレッド プールを使用するときに次のような疑問があるかどうかはわかりませんが、これは私が同僚から見たコードです。彼らはスレッド プールを次のように使用しています。

1. スレッド プールのローカル変数、new が使用され、手動シャットダウンは行われない
2. スレッド プールのローカル変数、new が使用され、最後に手動シャットダウンが実行される
3. スレッド プールはクラス再利用のための静的型として定義される。

まずはどの方法が正しいのか、間違った方法でどのような問題が発生するのかを考えて、疑問を持ちながらこの記事を読んでみましょう。

  この記事では、ローカル変数スレッド プールとクラス変数スレッド プールの使用における落とし穴と、その結果生じる可能性のある問題について説明します。最後に、経験と実際のオンライン プロジェクトに基づいて、スレッド プールの使用に関する私の現在の経験と、スレッド プールの作成におけるいくつかの変更点を、誰もが学べるように説明します。

方法 1: シャットダウンせずにスレッド プールをローカルで使用する

  まず最初に、このコードを実行するプログラムの各スレッドは、ローカル変数 new のスレッド プールからローカル スレッド プールを作成することを明確にします。各スレッドがスレッド プールを作成するという魔法の目的は言うまでもありませんが、スレッド プールの再利用可能な性質がまず壊れます。作成されたスレッド プールは再利用できません。なぜわざわざ多大な労力をかけてスレッド プールを作成する必要があるのでしょうか。

したがって、スレッド プール自体をローカルで使用することはお勧めできません。

  次に、もう一度考えてみましょう。スレッド プールをローカルで使用し、コア スレッドを 0 以外に設定し、allowCoreThreadTimeOut=false (コア スレッド プールはアイドル後にリサイクルされない) に設定すると、どのような問題が発生しますか? (考えないでください。コア スレッド プールはリサイクルできないため、当然 OOM が発生します)

問題のコードは次のとおりです。

public static void main(String[] args) {
    
    
        while (true) {
    
    
            try {
    
    

             //newFixedThreadPool不会回收核心线程 可能导致OOM
                ExecutorService service = Executors.newFixedThreadPool(1);
                service.submit(new Runnable() {
    
    
                    @Override
                    public void run() {
    
    
                        try {
    
    
                            Thread.sleep(2000); 模拟处理业务
                        } catch (InterruptedException e) {
    
    
                        }
                    }
                });
                service = null;
            } catch (Exception e) {
    
    
            }
            try {
    
    
                Thread.sleep(2000);
                System.gc();
            } catch (InterruptedException e) {
    
    
            }
        }
    }

  ならば、コアスレッドをリサイクル可能にすれば良いのではないだろうか?問題も発生する可能性があります。システムは、設定したスレッドの有効期限に基づいて、メモリ使用量が増加し、その後減少し、その後再び増加し、その後再び減少するという規則的な傾向を示す場合があります。これは良い記憶操作だと言えますか?

方法 2: スレッド プールをローカルで使用し、使用後にスレッド プールを手動でシャットダウンします。

  この方法では OOM のリスクは軽減されますが、ローカルで使用されます。スレッド プールをローカルで使用するのはなぜですか? これにより、各スレッドが新しいスレッド プールを作成し、スレッド プールが再利用されなくなりませんか? これとスレッド プールを使用しない場合の違いは何ですか? また、システムはスレッド プールを作成するためにリソースを無駄にします。

方法 3: クラス再利用のためにスレッド プールを静的タイプとして定義する

  明らかに、これはスレッド プールを使用する正しい方法です。静的に変更されたクラス変数は 1 回だけロードされ、すべてのスレッドがこのスレッド プールを共有します。正しい使用法コードは次のとおりです。

/**
 * @author: 代码丰
 * @Description:
 */
public class staticTestExample {
    
    
    //static 定义线程池
    private static ThreadPoolExecutor pool = new ThreadPoolExecutor(
            corePoolSize,
            maximumPoolSize,
            timeout,
            TimeUnit.SECONDS, 
            new LinkedBlockingDeque<>(), 
            new ThreadPoolExecutor.AbortPolicy());
   
    public static void main(String[] args) {
    
    
        //使用线程池
        Future<Boolean> submit = pool.submit(() -> true);
    }
}

ビジネスにおけるスレッドプールの再利用

  以下の2つのアイデアです

1 つ目: ビジネスの実行タイプに基づいてスレッド プールを作成する(同じ種類のビジネスではスレッド プールを再利用する)
  簡単に言うと、ビジネス シナリオが同じでスレッド プールが必要な場合、スレッド プールが再利用されます。たとえば、タスク シナリオを分割し、一度に 100 個のタスクを分割して実行する必要がある場合、同じビジネス シナリオのこれら 100 個のタスクを、処理のために特別に名前を付けたスレッド プールに引き渡すことができます。このスレッド プールは、タスク分割を処理するために特別に設計されています。

コードは以下のように表示されます。

/**
 * @author 代码丰
 * 创建线程池工具类
 */
public class ThreadPoolUtil {
    
    
    private static final Map<String, ExecutorService> POOL_CATCH = Maps.newConcurrentMap();

    /**
     * 根据特定名称去缓存线程池
     * @param poolName
     * @return
     */
    public static ExecutorService create(String poolName) {
    
    
        //map有缓存直接复用缓存中存在的线程池
        if (POOL_CATCH.containsKey(poolName)) {
    
    
            return POOL_CATCH.get(poolName);
        }
        // synchronized锁字符串常量池字段 防止并发,使得map中缓存的线程池,只会创建一次
        synchronized (poolName.intern()) {
    
    
            int poolSize = Runtime.getRuntime().availableProcessors() * 2;
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(poolSize, poolSize,
                    30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),new ThreadPoolExecutor.AbortPolicy());
            POOL_CATCH.put(poolName, threadPoolExecutor);
            return threadPoolExecutor;
        }
    }
}

	/**
 * @author: 代码丰
 */
public class CorrectTest {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService pool = ThreadPoolUtil.create("拆分任务线程池");
        //线程池执行任务
        Future<Boolean> submit = pool.submit(() -> true);
    }
}

2 番目のタイプ: ユーザー ログインの性質に基づいてスレッド プールを作成する(1 つのタイプのユーザーに対してスレッド プールを再利用する)
  簡単に言えば、ユーザー タイプとビジネス シナリオが同じで、スレッド プールが必要な場合は、スレッドプールは再利用されます。

/**
 * @author: 代码丰
 * @Description:
 */
public class CorrectTest {
    
    
    public static void main(String[] args) {
    
    
        //模拟得到用户信息
        Userinfo  = getUserinfo();
        //模拟用相同的用户类型(type)去创建线程池
        ExecutorService pool = ThreadPoolUtil.create(Userinfo.getType);
        //线程池执行任务
        Future<Boolean> submit = pool.submit(() -> true);
    }
}

要約する

  1. ローカル スレッド プールの代わりにグローバル スレッド プールを使用します。そうしないと、ローカル スレッド プールが継続的に作成される OOM リスクが発生する可能性があります。
  2. ローカル スレッド プールを使用する場合でも、最後にシャットダウンする必要があります。シャットダウンしないと、コア スレッドをリサイクルしないメモリ リークが発生する可能性があります。
  3. スレッド プールは再利用するものであることを理解し、コード内でローカル スレッド プールをランダムに作成しないでください。

おすすめ

転載: blog.csdn.net/qq_44716086/article/details/128922458