Detailed explanation of thread pool creation methods (7 types)

1. Creation of thread pool

There are 7 methods of creating thread pools, but they can be generally divided into 2 categories:

1. 通过 ThreadPoolExecutor 创建的线程池;
2. 通过 Executors 创建的线程池。

There are a total of 7 ways to create a thread pool (6 of which are created through Executors, and 1 is created through ThreadPoolExecutor):

1. Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。

2 A fixed number of thread pools (newFixedThreadPool)

public static class FixedThreadPool {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        ExecutorService es = Executors.newFixedThreadPool(2);
        // 方式1
        Future<String> submit = es.submit(() -> Thread.currentThread().getName());
        System.out.println(submit.get());
        // 方式2
        es.execute(() -> System.out.println(Thread.currentThread().getName()));
    }
}
输出:
pool-1-thread-1
pool-1-thread-2

2.1 The difference between execute and submit

  • Both execute and submit belong to the method of thread pool, and execute can only submit tasks of type Runnable
  • submit can submit both Runnable type tasks and Callable type tasks.
  • execute() has no return value
  • submit has a return value, so you must use submit when you need a return value
使用submit可以执行有返回值的任务或者是无返回值的任务;而execute只能执行不带返回值的任务。 

2.2 Customize thread pool name or priority

/**
     * 定义线程池名称或优先级
     */
public static class FixedThreadPoll {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 创建线程工厂
        ThreadFactory threadFactory = r -> {
    
    
            // !!!!!!!一定要注意:要把任务Runnable设置给新创建的线程
            Thread thread = new Thread(r);
            // 设置线程的命名规则
            thread.setName("我的线程:" + r.hashCode());
            // 设置线程的优先级
            thread.setPriority(Thread.MAX_PRIORITY);
            return thread;
        };
        ExecutorService es = Executors.newFixedThreadPool(2, threadFactory);
        // 参数是接口类,所以lambda的()->{}在括号里面,上面的r->{}是直接一个类,并不是作为入参
        Future<Integer> result = es.submit(() -> {
    
    
            int num = new Random().nextInt(10);
            System.out.println("线程优先级" + Thread.currentThread().getPriority() + ", 随机数:" + num);
            return num;
        });
        // 打印线程池返回结果
        System.out.println("返回结果:" + result.get());
    }
}
输出:
线程优先级10, 随机数:0
返回结果:0
提供的功能:

        1. 设置(线程池中)线程的命名规则。

        2. 设置线程的优先级。

        3. 设置线程分组。

        4. 设置线程类型(用户线程、守护线程)。

3 thread pool with cache (newCachedThreadPool)

public static class CachedThreadPool1 {
    
    
    public static void main(String[] args) {
    
    
        // 创建线程池
        ExecutorService es = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
    
    
            int finalI = i;
            es.submit(() -> System.out.println("i : " + finalI + "|线程名称:" + Thread.currentThread().getName()));
        }
    }
}
输出:
i : 1|线程名称:pool-1-thread-2
i : 2|线程名称:pool-1-thread-3
i : 0|线程名称:pool-1-thread-1    
优点:线程池会根据任务数量创建线程池,并且在一定时间内可以重复使用这些线程,产生相应的线程池。

缺点:适用于短时间有大量任务的场景,它的缺点是可能会占用很多的资源。

4 Single thread thread pool (newSingleThreadExecutor)

public static class SingleThreadExecutor1 {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService es = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
    
    
            es.submit(() -> System.out.println("线程名:" + Thread.currentThread().getName()));
        }
    }
}
输出:
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1    
单线程的线程池又什么意义?
1. 复用线程。
2. 单线程的线程池提供了任务队列和拒绝策略(当任务队列满了之后(Integer.MAX_VALUE),新来的任务就会拒绝策略)

5 Threads that execute scheduled tasks (newScheduledThreadPool)

5.1 Delayed execution (once)

public static class ScheduledThreadPool1 {
    
    
    public static void main(String[] args) {
    
    
        ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
        System.out.println("添加任务的时间:" + LocalDateTime.now());
        ses.schedule(() -> System.out.println("执行子任务:" + LocalDateTime.now()), 3, TimeUnit.SECONDS);
    }
}
输出:
添加任务的时间:2022-06-13T18:57:19.742726900
执行子任务:2022-06-13T18:57:22.758147900    

5.2 Fixed frequency execution

public static void main(String[] args) {
    
    
    ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
    System.out.println("添加任务的时间:" + LocalDateTime.now());
    ses.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + "执行任务:" + LocalDateTime.now()), 2, 4, TimeUnit.SECONDS);
    // 2是第一次多久之后执行,4是定时任务每隔多久执行一次
}
输出:
添加任务的时间:2022-06-13T19:08:21.725930300
pool-1-thread-1执行任务:2022-06-13T19:08:23.741930800
pool-1-thread-1执行任务:2022-06-13T19:08:27.736881200
pool-1-thread-2执行任务:2022-06-13T19:08:31.742574400
pool-1-thread-2执行任务:2022-06-13T19:08:35.743332900    
如果在定义定时任务中设置了内部睡眠时间,比如
    ses.scheduleAtFixedRate((Runnable) () -> {
    
    
        System.out.println("执行任务: " + LocalDateTime.now());
        try {
    
    
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    },2,4, TimeUnit.SECONDS);   
5 > 4 ,哪个值大以哪个值作为定时任务的执行周期

5.3 scheduleAtFixedRate VS scheduleWithFixedDelay

public static class ScheduledThreadPool3 {
    
    
    public static void main(String[] args) {
    
    
        // 创建线程池
        ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
        System.out.println("添加任务时间:" + LocalDateTime.now());
        // 2s之后开始执行定时任务,定时任务每隔4s执行一次
        ses.scheduleWithFixedDelay(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "执行任务:" + LocalDateTime.now());
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }, 2, 4, TimeUnit.SECONDS);
    }
}
输出:
添加任务时间:2022-06-13T19:20:34.311246
pool-1-thread-1执行任务:2022-06-13T19:20:36.317013100
pool-1-thread-1执行任务:2022-06-13T19:20:41.340114100
pool-1-thread-2执行任务:2022-06-13T19:20:46.362309400
pool-1-thread-2执行任务:2022-06-13T19:20:51.369756400    
scheduleAtFixedRate 是以上⼀次任务的开始时间,作为下次定时任务的参考时间的
scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的。

6 Scheduled task single thread (newSingleThreadScheduledExecutor)

public static class SingleThreadScheduledExecutor1 {
    
    
    public static void main(String[] args) {
    
    
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        System.out.println("添加任务时间:" + LocalDateTime.now());
        ses.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + "执行时间:" + LocalDateTime.now()), 2, 4, TimeUnit.SECONDS);
    }
}
输出:
添加任务时间:2022-06-13T19:27:06.143427900
pool-1-thread-1执行时间:2022-06-13T19:27:08.146768100
pool-1-thread-1执行时间:2022-06-13T19:27:12.156625
pool-1-thread-1执行时间:2022-06-13T19:27:16.158957500
pool-1-thread-1执行时间:2022-06-13T19:27:20.156330800    

7 Generate a thread pool based on the current CPU (newWorkStealingPool)

public static class WorkStealingPool1 {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService service = Executors.newWorkStealingPool();
        for (int i = 0; i < 5; i++) {
    
    
            service.submit(() -> {
    
    
                System.out.println("线程名:" + Thread.currentThread().getName());
            });
            while (!service.isTerminated()) {
    
    
            }
        }
    }
}
输出:
线程名:ForkJoinPool-1-worker-1    

8 ThreadPoolExecutor

ThreadPoolExecutor inherits from AbstractExecutorService, and AbstractExecutorService implements the ExecutorService interface

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize: the maximum number of core threads in the thread pool
  • maximumPoolSize : The thread pool can have the maximum number of threads
  • workQueue: a blocking queue for caching tasks

When calling the thread pool execute() method to add a task, the thread pool will make the following judgments:

1. If there is an idle thread, execute the task directly;
2. If there is no idle thread, and the number of currently running threads is less than corePoolSize, create a new thread to execute the task;
3. If there is no idle thread, and the current thread If the number is equal to corePoolSize, and the blocking queue is not full, the task will be queued without adding a new thread;
4. If there are no idle threads, and the blocking queue is full, and the number of threads in the pool is less than maximumPoolSize, a new thread will be created Execute the task;
5. If there is no idle thread, and the blocking queue is full, and the number of threads in the pool is equal to maximumPoolSize, then reject new tasks according to the strategy specified by the handler in the constructor.

1) Bounded queue:

SynchronousQueue: A blocking queue that does not store elements. Each insertion operation must wait until another thread calls the removal operation, otherwise the insertion operation is always blocked. The throughput is usually higher than LinkedBlockingQueue. The static factory method Executors.newCachedThreadPool uses this queue .
ArrayBlockingQueue: A bounded blocking queue backed by an array. This queue sorts the elements on a FIFO (first in first out) basis. Once such a cache is created, its capacity cannot be increased. Attempting to put an element into a full queue will cause the operation to block; trying to extract an element from an empty queue will similarly block.
2) Unbounded queue:

LinkedBlockingQueue: An unbounded blocking queue based on a linked list structure, which can specify capacity or not (in fact, any queue/stack with unlimited capacity has capacity, and this capacity is Integer.MAX_VALUE) PriorityBlockingQueue: It is a priority
block An unbounded blocking queue with internal element ordering. The elements in the queue must implement the Comparable interface so that they can be sorted by implementing the compareTo() method. The element with the highest priority will always be at the head of the queue; PriorityBlockingQueue does not guarantee the ordering of elements of the same priority.

Link: Use of thread pool (7 creation methods) Detailed explanation of ThreadPoolExecutor

Guess you like

Origin blog.csdn.net/qq_41821963/article/details/125341789