[Java concurrent programming] thread pool related knowledge points finishing

Why use thread pool?

Pooling technology: Reduce the consumption of resources each time and improve the utilization of resources.

The thread pool provides a way to limit and manage resources (including the execution of a task). Each thread pool also maintains some basic statistics, such as the number of completed tasks.

Benefits of using thread pool:

  • Reduce resource consumption . Reduce the consumption caused by thread creation and destruction by reusing the created threads.
  • Improve response speed . When the task arrives, the task can be executed immediately without waiting until the thread is created.
  • Improve the manageability of threads . Threads are scarce resources. If they are created unlimitedly, they will not only consume system resources, but also reduce the stability of the system. The thread pool can be used for uniform allocation, tuning and monitoring.

Recommended viewing: Portal

The realization principle of thread pool?

execute method source code

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

When the thread pool creates a thread, it encapsulates the thread into a worker thread. After the worker completes the task, it will cyclically obtain the tasks from the work queue for execution.

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

 

"Java Concurrent Programming" thread pool related knowledge points

 

  1. The thread pool judges whether the threads in the core thread pool [corePoolSize] are all performing tasks. If it is not, create a new worker thread to perform the task. If the threads in the core thread pool are all performing tasks, enter the next process.
  2. The thread pool judges whether the work queue [BlockingQueue] is full. If the work queue is not full, the newly submitted tasks are stored in this work queue. If the work queue is full, enter the next process.
  3. The thread pool judges whether the threads of the thread pool [maximumPoolSize] are all working. If not, create a new worker thread to perform the task. If it is full, hand over to the saturation strategy [RejectedExecutionHandler.rejectedExecution()] to handle this task.

 

"Java Concurrent Programming" thread pool related knowledge points

 

How to use thread pool?

Important analysis of ThreadPoolExecutor

Important parameters of the construction method

There are many construction parameters of the ThreadPoolExecutor method, we can look at the longest one:

    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: The number of core threads defines the minimum number of threads that can run simultaneously .
  • maximumPoolSize: When the tasks stored in the queue reach the queue capacity , the number of threads that can currently run at the same time becomes the maximum number of threads . [If you use an unbounded queue, this parameter has no effect]
  • workQueue: When a new task comes, it will first determine whether the number of currently running threads reaches the number of core threads. If it reaches the number of core threads, the new task will be stored in the queue .
  • keepAliveTime: When the number of threads in the thread pool is greater than corePoolSize, if no new tasks are submitted at this time, threads outside the core thread will not be destroyed immediately, but will wait until the waiting time exceeds keepAliveTime before being recycled and destroyed .
  • unit: the time unit of keepAliveTime.
  • threadFactory: used to set the factory for creating threads. You can set a more meaningful name for each created thread through the thread factory.
  • handler: Saturation strategy. When the number of threads currently running at the same time reaches the maximum number of threads [maximumPoolSize] and the queue is full, the saturation strategy is executed.

Simple use of thread pool

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

What are the task queues?

  • ArrayBlockingQueue: A bounded blocking queue based on the array structure, which sorts the elements according to the FIFO principle.
  • LinkedBlockingQueue: Blocking queue based on the linked list structure, sorting elements by FIFO, throughput is usually higher than ArrayBlockingQueue, Executors.newFixedThreadPool() uses this queue.
  • SynchronousQueue: A blocking queue that does not store elements. Each insert operation must wait until another thread calls the removal operation, otherwise the insert operation has been blocked, and the throughput is usually higher than LinkedBlockingQueue. This queue is used by Executors.newCachedThreadPool() .
  • PriorityBlockingQueue: An infinite blocking queue with priority.

What are the saturation strategies?

  • ThreadPoolExecutor.AbortPolicy : Throw a RejectedExecutionException to reject the processing of the new task. [Default saturation strategy]
  • ThreadPoolExecutor.CallerRunsPolicy [Provide a scalable queue]: Call to execute your own thread to run the task, that is, run the rejected task directly in the thread calling the execute method. If the execution program is closed, the task will be discarded . Therefore, this strategy will reduce the speed of submitting new tasks and affect the overall performance of the program. If your application can tolerate this delay and you require any task request to be executed, you can choose this strategy. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {r.run();}}
  • ThreadPoolExecutor.DiscardPolicy:  Do not process new tasks, just discard them.
  • ThreadPoolExecutor.DiscardOldestPolicy:  This policy will discard the oldest unprocessed task request and execute the current task. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {e.getQueue().poll(); e.execute(r);}}

Of course, you can also customize the rejection strategy according to your needs, and you need to implement RejectedExecutionHandler.

How to create a thread pool?

1. Use various construction methods of ThreadPoolExecutor.

2. Three types of ThreadPoolExecutor can be created through Executors, the tool class of the Executor framework.

The forced thread pool in "Alibaba Java Development Manual" does not allow the use of Executors to create it, but uses the ThreadPoolExecutor method . This processing method allows students to write more clearly the operating rules of the thread pool and avoid the risk of resource exhaustion

The drawbacks of Executors returning thread pool objects are as follows:  FixedThreadPool and SingleThreadExecutor : The queue length allowed for requests is Integer.MAX_VALUE, which may accumulate a large number of requests, leading to OOM. CachedThreadPool and ScheduledThreadPool  : The number of threads allowed to be created is Integer.MAX_VALUE, which may create a large number of threads, leading to OOM.

What is the difference between execute method and submit method?

  • The execute() method is used to submit tasks that do not require a return value, so it is impossible to determine whether the task is successfully executed by the thread pool;
threadPool.execute(new Runnable() {
    @Override
    public void run() {
    }}); //通过execute提交
  • The submit() method is used to submit tasks that require return values. The thread pool will return an object of type Future. This Future object can be used to determine whether the task is executed successfully , and the return value can be obtained through the get() method of Future. The get() method will block the current thread until the task is completed, and use get The (long timeout, TimeUnit unit) method will block the current thread for a period of time and return immediately. At this time, the task may not be completed.
Future<Object> future = threadPool.submit(hasReturnValueTask);
try{
    Object s = future.get();
}catch(InterruptedException e){
    //处理中断异常
}catch(ExecutionException e){
    //处理无法执行任务异常
}finally{
    threadPool.shutdown();
}

If you think this article is helpful to you, you can like it and follow it to support it, or you can follow my public account, there are more technical dry goods articles and related information sharing, everyone can learn and progress together!

 

Guess you like

Origin blog.csdn.net/weixin_50205273/article/details/108682056