Easy to understand! Detailed Java thread pool

Easy to understand!  Detailed Java thread pool

Why do we need several parameters to construct a thread pool? How to avoid OOM in the thread pool? What is the difference between Runnableand Callable? This article will answer these questions one by one, and will also give common scenarios and code snippets using thread pools.

Basic knowledge

Executors create thread pool

Creating a thread pool in Java is very simple, you only need to call Executorsthe corresponding convenience methods, for example Executors.newFixedThreadPool(int nThreads), but convenience not only hides the complexity, but also bury potential hidden dangers (OOM, thread exhaustion) for us.

ExecutorsList of convenient methods to create a thread pool:

264538c79c4058f69030c061231dbf28.jpeg

There is nothing wrong with using these shortcuts for small programs. For programs that need to be run for a long time on the server side, create a thread pool that should be used directly ThreadPoolExecutor. That's right, Executorsthe thread pool created by the above method is ThreadPoolExecutor.

ThreadPoolExecutor construction method

ExecutorsThe shortcut method of creating a thread pool in is actually the called ThreadPoolExecutorconstruction method (which is used by timed tasks ScheduledThreadPoolExecutor). The parameter list of this type of construction method is as follows:

// The complete constructor of the Java thread pool public ThreadPoolExecutor(
  int corePoolSize, // The number of threads that the thread pool maintains for a long time, even if the thread is in the Idle state, it will not be recycled. int maximumPoolSize, // The upper limit of the number of threads long keepAliveTime, TimeUnit unit, // The idle duration of threads exceeding corePoolSize, // After this time, the excess threads will be recycled. BlockingQueue<Runnable> workQueue, // Queue queue of tasks ThreadFactory threadFactory, // How to generate new threads RejectedExecutionHandler handler) // Rejection strategy

There are 7 parameters, so helpless, so many parameters are really needed to construct a thread pool. These parameters are, the more likely to cause problems are corePoolSize, maximumPoolSize, workQueueand handler:

  • corePoolSizeAnd maximumPoolSizeimproper settings will affect efficiency and even exhaust threads;
  • workQueueImproper setting can easily lead to OOM;
  • handlerImproper setting will cause an exception to be thrown when submitting the task.

The correct parameter setting method will be given below.

Working order of thread pool

If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.

corePoolSize -> task queue -> maximumPoolSize -> rejection strategy

Runnable和Callable

There are two types of tasks that can be submitted to the thread pool: Runnableand Callable, the difference between the two is as follows:

  1. Different method signature, void Runnable.run(),V Callable.call() throws Exception
  2. Whether to allow return value, Callableallow return value
  3. Whether to allow exceptions to Callablebe thrown , allow exceptions to be thrown.

CallableIt is an interface added in JDK1.5. As Runnablea supplement, it allows return values ​​and exceptions to be thrown.

Three ways to submit tasks:

3e714d27f23e4834fd91a7e8228259e2.jpeg

How to use thread pool correctly

Avoid using *** queue

Do not use the Executors.newXXXThreadPool()shortcut method to create the thread pool, because this method will use the task queue of ***, in order to avoid OOM, we should use ThreadPoolExecutorthe construction method to manually specify the maximum length of the queue:

ExecutorService executorService = new ThreadPoolExecutor(2, 2, 
				0, TimeUnit.SECONDS, 
				new ArrayBlockingQueue<>(512), // Use a bounded queue to avoid OOM new ThreadPoolExecutor.DiscardPolicy());

Behavior when explicitly rejecting the task

When the task queue is always full, what submit()happens when a new task is submitted? RejectedExecutionHandlerThe interface provides us with a control method, and the interface is defined as follows:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);}

The thread pool provides us with several common rejection strategies:

9e8acc260566a4c96902faeba90f8e25.jpegfb72e00e495d923a9f09d1a65dcfc660.jpeg

The default rejection behavior of the thread pool AbortPolicyis to throw RejectedExecutionHandleran exception, which is an unchecked exception and it is easy to forget to catch it. If you don't care about the event that the task is rejected, you can set the rejection policy to DiscardPolicythis so that redundant tasks will be silently ignored.

ExecutorService executorService = new ThreadPoolExecutor(2, 2, 
				0, TimeUnit.SECONDS, 
				new ArrayBlockingQueue<>(512), 
				new ThreadPoolExecutor.DiscardPolicy());// Specify rejection policy

Get processing results and exceptions

The processing results of the thread pool and the exceptions during the processing are packaged Futurein and obtained when the Future.get()method is called . The exceptions during the execution will be packaged ExecutionException. The submit()method itself will not transmit the results and exceptions during the task execution. The code to get the execution result can be written like this:

ExecutorService executorService = Executors.newFixedThreadPool(4);Future<Object> future = executorService.submit(new Callable<Object>() {
        @Override
        public Object call() throws Exception {
            throw new RuntimeException("exception in call~");// The exception will be passed to the caller when Future.get() is called}
    });
	try {
  Object result = future.get();} catch (InterruptedException e) {
  // interrupt} catch (ExecutionException e) {
  // exception in Callable.call()  e.printStackTrace();}

The output of the above code is similar to the following:

14690ba563d1eebf77744d71afb69c6d.jpeg

Common scenarios for thread pools

Correctly construct the thread pool

int poolSize = Runtime.getRuntime().availableProcessors() * 2;BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(512);RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy();executorService = new ThreadPoolExecutor(poolSize, poolSize,
    0, TimeUnit.SECONDS,
            queue,
            policy);

Get a single result

After submit()submitting a task to the thread pool, one will be returned Future. The calling V Future.get()method can block waiting for the execution result, and the V get(long timeout, TimeUnit unit)method can specify the timeout period for waiting.

Get multiple results

If multiple tasks are submitted to the thread pool, to get the execution results of these tasks, you can call Future.get()get in turn . But for this scenario, we should use ExecutorCompletionService . The take()method of this class always blocks waiting for a certain task to complete, and then returns the Futureobject of the task . CompletionServiceAfter submitting tasks to the batch, you only need to call the same number of CompletionService.take()methods to obtain the execution results of all tasks. The order of obtaining is arbitrary, depending on the order of completion of the tasks:

void solve(Executor executor, Collection<Callable<Result>> solvers)
   throws InterruptedException, ExecutionException {
   
   CompletionService<Result> ecs = new ExecutorCompletionService<Result>(executor);// 构造器   
   for (Callable<Result> s: solvers)// Submit all tasks ecs.submit(s);
	   
   int n = solvers.size();
   for (int i = 0; i <n; ++i) {// Get every completed task Result r = ecs.take().get();
       if (r != null)
           use(r);
   }}

Timeout of a single task

V Future.get(long timeout, TimeUnit unit)The method can specify the timeout time to wait, and the timeout will be thrown if it is not completed TimeoutException.

Timeout for multiple tasks

Wait for multiple tasks to complete, and set the maximum waiting time, which can be done through CountDownLatch :

public void testLatch(ExecutorService executorService, List<Runnable> tasks) 
	throws InterruptedException{
      
    CountDownLatch latch = new CountDownLatch(tasks.size());
      for(Runnable r : tasks){
          executorService.submit(new Runnable() {
              @Override
              public void run() {
                  try{
                      r.run();
                  }finally {
                      latch.countDown();// countDown                  }
              }
          });
      }
	  latch.await(10, TimeUnit.SECONDS); // Specify the timeout period}

Thread pool and decoration company

Take the analogy of operating a decoration company. The company waits for customers to submit decoration requests at the office; the company has a fixed number of formal workers to maintain operation; when there are more business in peak seasons, new customer requests will be scheduled, for example, after receiving an order, tell the user to start decoration after one month; When there are too many schedules, in order to avoid users waiting too long, the company will hire some temporary workers through certain channels (such as the talent market, acquaintance introductions, etc.) (note that the recruitment of temporary workers is after the expiration of the schedule); if temporary workers It is too busy, the company will decide not to accept new customers, and directly reject the order.

The thread pool is the "decoration company" in the program, doing all sorts of dirty work. The above process corresponds to the thread pool:

// The complete constructor of the Java thread pool public ThreadPoolExecutor(
  int corePoolSize, // The number of formal workers int maximumPoolSize, // The maximum number of workers, including regular and temporary workers long keepAliveTime, TimeUnit unit, // The longest time that temporary workers are idle, and will be fired if this time is exceeded. BlockingQueue<Runnable> workQueue , // ThreadFactory threadFactory, // hiring channel RejectedExecutionHandler handler) // Rejection method

to sum up

ExecutorsProvides us with a convenient way to construct a thread pool. For server programs, we should put an end to these convenient methods, but directly use the thread pool ThreadPoolExecutorconstruction method to avoid OOM that may be caused by the *** queue and threads caused by improper limit on the number of threads Problems such as running out of numbers. ExecutorCompletionServiceProvides an effective way to wait for the completion of all tasks. If you want to set the waiting timeout time, you can CountDownLatchcomplete it.

Refer to
ThreadPoolExecutor API Doc

Author: CarpenterLee
description link: the Java thread pool Detailed - CarpenterLee - blog Park,
the original source: blog Garden
invasion deleted

bb4fcfdb750d25fd19ae691b4fab3356.jpeg


Guess you like

Origin blog.51cto.com/15050718/2621802