[Javaインタビューの質問] Ali、JD、ByteDanceなどのインターネット企業が尋ねる必要がある並行プログラミングおよびスレッドプール関連の知識ポイント

迷わないように注意してください。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);        }    }

ここに画像の説明を挿入

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

ここに画像の説明を挿入

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

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®;}}

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

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

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

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

「Alibaba Java開発マニュアル」(このマニュアルを無料で入手するには、ここをクリックしてください)強​​制スレッドプールでは、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/yueyunyin/article/details/108752803