ThreadPoolExecutor actual combat (with source code demo)

Use of ThreadPoolExecutor

ThreadPoolExecutor provides four construction methods:

img

Use the last construction method (the one with the most parameters) to explain its parameters:

 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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
Serial number name Types of meaning
1 corePoolSize int Core thread pool size
2 maximumPoolSize int Maximum thread pool size
3 keepAliveTime long Maximum idle time of threads
4 unit TimeUnit time unit
5 workQueue BlockingQueue Thread waiting queue
6 threadFactory ThreadFactory Thread creation factory
7 handler RejectedExecutionHandler Rejection strategy

After knowing the role of each parameter, we began to construct a thread pool that met our expectations. First look at the several thread pools predefined by the JDK:

One, predefined thread pool

1.FixedThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {
    
    
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    }
  • corePoolSize is equal to maximumPoolSize, that is, its threads are all core threads, which is a fixed-size thread pool, which is its advantage;
  • keepAliveTime = 0 This parameter is invalid for core threads by default, and FixedThreadPool is all core threads;
  • workQueue is LinkedBlockingQueue (unbounded blocking queue), the maximum value of the queue is Integer.MAX_VALUE. If the task submission speed continues to exceed the task processing speed, it will cause a lot of congestion in the queue. Because the queue is very large, it is very likely that the memory will overflow before the policy is rejected. Is its disadvantage;
  • The task execution of FixedThreadPool is disorderly;

Applicable scenarios: It can be used for instantaneous peak clipping of Web services, but it is necessary to pay attention to queue congestion caused by long-term continuous peak conditions.

2.CachedThreadPool

     public static ExecutorService newCachedThreadPool() {
    
    
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    }
  • corePoolSize = 0, maximumPoolSize = Integer.MAX_VALUE, that is, the number of threads is almost unlimited;
  • keepAliveTime = 60s, the thread will end automatically after being idle for 60s.
  • workQueue is a SynchronousQueue synchronous queue, this queue is similar to a baton, the queue must be delivered at the same time, because the CachedThreadPool thread is created unlimited, there will be no queue waiting, so use SynchronousQueue;
  • Applicable scenarios: Fast processing of a large number of tasks with a short time-consuming, such as Netty NIO when accepting requests, you can use CachedThreadPool.

2.SingleThreadPool

public static ExecutorService newSingleThreadExecutor() {
    
    
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
    }

This look is newFixedThreadPool(1). There is an additional layer of FinalizableDelegatedExecutorService packaging. What is the use of this layer? Write a dome to explain:

  public static void main(String[] args) {
    
    
        ExecutorService fixedExecutorService = Executors.newFixedThreadPool(1);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) fixedExecutorService;
        System.out.println(threadPoolExecutor.getMaximumPoolSize());
        threadPoolExecutor.setCorePoolSize(8);
        
        ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();
//      运行时异常 java.lang.ClassCastException
//      ThreadPoolExecutor threadPoolExecutor2 = (ThreadPoolExecutor) singleExecutorService;
    }

It can be seen from the comparison that FixedThreadPool can be transformed down to ThreadPoolExecutor, and its thread pool can be configured, while SingleThreadExecutor cannot be successfully transformed down after being packaged. Therefore, after the SingleThreadExecutor is set, it cannot be modified, and a real Single is achieved.

Two, custom thread pool

The Ali standard also recommends the second method of creating a thread pool:

img

The following is a demo of a custom thread pool, using a bounded queue, custom ThreadFactory and rejection strategy:

public class ThreadTest {
    
    

    public static void main(String[] args) throws InterruptedException, IOException {
    
    
        int corePoolSize = 2;
        int maximumPoolSize = 4;
        long keepAliveTime = 10;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
        ThreadFactory threadFactory = new NameTreadFactory();
        RejectedExecutionHandler handler = new MyIgnorePolicy();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,
                workQueue, threadFactory, handler);
        executor.prestartAllCoreThreads(); // 预启动所有核心线程
        
        for (int i = 1; i <= 10; i++) {
    
    
            MyTask task = new MyTask(String.valueOf(i));
            executor.execute(task);
        }

        System.in.read(); //阻塞主线程
    }

    static class NameTreadFactory implements ThreadFactory {
    
    

        private final AtomicInteger mThreadNum = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
    
    
            Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());
            System.out.println(t.getName() + " has been created");
            return t;
        }
    }

    public static class MyIgnorePolicy implements RejectedExecutionHandler {
    
    

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
            doLog(r, e);
        }

        private void doLog(Runnable r, ThreadPoolExecutor e) {
    
    
            // 可做日志记录等
            System.err.println( r.toString() + " rejected");
//          System.out.println("completedTaskCount: " + e.getCompletedTaskCount());
        }
    }

    static class MyTask implements Runnable {
    
    
        private String name;

        public MyTask(String name) {
    
    
            this.name = name;
        }

        @Override
        public void run() {
    
    
            try {
    
    
                System.out.println(this.toString() + " is running!");
                Thread.sleep(3000); //让任务执行慢点
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        public String getName() {
    
    
            return name;
        }

        @Override
        public String toString() {
    
    
            return "MyTask [name=" + name + "]";
        }
    }
}

The output is as follows:

img

Guess you like

Origin blog.csdn.net/virtual_users/article/details/110239188