Copyright notice: this article is the blogger’s original article, without the permission of the blogger shall not be reproduced source: github.com/AnliaLee if you see any mistakes or good suggestions, welcome to leave a comment

preface

In this blog we will begin to explore the ThreadPoolExecutor that was introduced in the previous chapter. Due to the large number of examples included, the article is a little long, please be patient to eat…

A synchronized Handler is used to parse the communication mechanism between Android threads and Runnable threads Android multithreading (4) Callable, Future and FutureTask


ThreadPoolExecutor profile

Introduce this thing also can’t write what pattern come, then direct lazy quote others of ha ha

Why thread pools?

Disadvantages of new Thread() • Each new Thread() costs performance • Threads created by calling new Thread() are poorly managed and called wild threads. They can be created without limit and compete with each other, which will occupy too many system resources and lead to system breakdown • Not good for scaling. For example, the advantages of thread pool, such as periodic execution, periodic execution, thread interruption • Reuse of existing threads, reduce the overhead of object creation and death, good performance • Can effectively control the maximum number of concurrent threads, improve the utilization rate of system resources, while avoiding excessive resource competition. • Provides scheduled execution, periodic execution, single thread, concurrency control and other functions

The above is excerpted from the Android Thread Management ExecutorService thread pool

The inheritance relationship of thread pool ThreadPoolExecutor is shown below

In the next section, we’ll look at the construction parameters of ThreadPoolExecutor


Argument parsing

To construct a ThreadPoolExecutor, we use the constructor with the most arguments. (Since threadFactory and handler have default values, the difference is that there is no entry to set these parameters.)

public ThreadPoolExecutor(int corePoolSize,
			  int maximumPoolSize,
			  long keepAliveTime,
			  TimeUnit unit,
			  BlockingQueue<Runnable> workQueue,
			  ThreadFactory threadFactory,
			  RejectedExecutionHandler handler)
Copy the code

We put metrics into the story of programmer development to make it easier to understand. Here are the metrics

  • Int corePoolSize: Number of core programmers you plan to hire. Core programmers are the backbone of the company, and the company will give priority to core programmers to develop after receiving demands (tasks) from Party A

    The number of core threads in the thread pool

  • Int maximumPoolSize: Total number of programmers planned to be hired. Programmers are made up of core programmers and interns

    The maximum number of threads in the thread pool

  • Long keepAliveTime: The length of time that employees are allowed to play with soy sauce. Companies hire interns and fire them if they find that there is no work to be done during a period of time when they are slacking off on nuggets. Core programmers embrace, of course also is not necessarily the iron rice bowl, if the company take the cost savings business strategy (ThreadPoolExecutor. AllowCoreThreadTimeOut is set to true), core programmers for a period of time didn’t do the same will be layoffs

    The idle time of a thread. By default, this parameter applies only to non-core threads, that is, non-core threads are reclaimed when their idle time exceeds keepAliveTime. But if ThreadPoolExecutor allowCoreThreadTimeOut is set to true, then the parameters can also apply to core thread

  • TimeUnit Unit: indicates the unit of time parameters, including nanosecond, microsecond, millisecond, second, minute, hour, and day

    Available unit types are: timeunit. NANOSECONDS: NANOSECONDS timeunit. MICROSECONDS: MICROSECONDS timeunit. MILLISECONDS: MILLISECONDS timeunit. SECONDS: MINUTES: MINUTES timeunit. HOURS: HOURS timeunit. DAYS: DAYS

  • BlockingQueue

    workQueue: Queue of reserved tasks

    A task queue in a thread pool that is used to store tasks that have been submitted but not yet assigned to threads for execution. • ArrayBlockingQueue: array based BlockingQueue • LinkedBlockingQueue: list based BlockingQueue • PriorityBlockingQueue: array based BlockingQueue Priority-based blocking queues • DelayQueue • SynchronousQueue we will compare the differences between these queues in detail in the following sections. In addition, it is important to note that all incoming tasks implement the Runnable interface

  • ThreadFactory ThreadFactory:

    The Thread factory interface, with a single new Thread(Runnable R) method, creates new threads for the Thread pool. System provides us with the default threadFactory: Executors. DefaultThreadFactory (), we generally use the default

  • RejectedExecutionHandler handler:

    Refused to strategy, default ThreadPoolExecutor AbortPolicy, when a new task rejection will throw RejectExecutorException anomalies. There are three other policies to choose from: CallerRunsPolicy, DiscardPolicy and DiscardOldestPolicy


ThreadPoolExecutor task execution policy

When we submit a task to the thread pool using the Submit or execute methods, the thread pool allocates the task to the appropriate thread for execution following the following policy (the task queue uses the basic ArrayBlockingQueue)

  • HR decides when to recruit according to the task executionCore programmerIf a request is received after discoveryCore programmerHave tasks on hand (orNot a single programmer“), will recruit one,Filled so far

    After a task is submitted, if the number of threads in the thread pool does not reach the number of core threads (corePoolSize), a core thread is created to execute it

For example, if we set the number of tasks (taskSize) to 3 and the number of core threads (corePoolSize) to 5, then taskSize < corePoolSize will create 3 core threads to execute the task (assuming each task takes a certain amount of time to complete).

public class ExecutorTest {
    // omit some code...
    private static int taskSize = 3;/ / number of jobs
    private static int corePoolSize = 5;// Number of core threads
    private static int maximumPoolSize = 20;// Maximum number of threads
    private static int queueSize = 128;// The number of tasks that can be stored

    public static class TestTask implements Runnable {
        public void run(a) {
            if (taskSize > 0) {
                try{
                    Thread.sleep(500);// Simulate development time
                    System.out.println(getTime() + getName(Thread.currentThread().getName())
                            + "Complete a development task with number T" + (taskSize--)
                    );
                }catch(Exception e){ e.printStackTrace(); }}}}public static void main(String args[]){
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(
                        corePoolSize,
                        maximumPoolSize,
                        1,
                        TimeUnit.SECONDS,
                        new ArrayBlockingQueue<Runnable>(queueSize)
                );
        TestTask task;
        int size = taskSize;
        for (int i = 0; i < size; i++) {
            task = newTestTask(); executor.execute(task); } executor.shutdown(); }}Copy the code

The result is shown below (please ignore the task number, it is not available yet).


  • When another assignment comes down, ifCore programmersomeonefreeAnd threw it to him; If there are tasks in hand, save them toIn the workQueueWait until someone is free to do it. This, of course,The queueCan be storedNumber of tasksisIs the limited

    After a task is submitted, if the number of threads in the thread pool reaches the number of core threads (corePoolSize) but the number of tasks stored in the workQueue does not reach the maximum value, the task is put into the task queue for execution

We set the number of tasks to 10, the number of core threads to 3, and the maximum size of the task queue to 7, at which point tasks assigned to the core thread will fill the task queue just enough

private static int taskSize = 10;/ / number of jobs
private static int corePoolSize = 3;// Number of core threads
private static int maximumPoolSize = 10;// Maximum number of threads
private static int queueSize = 7;// The number of tasks that can be stored
Copy the code

The running result is shown in the following figure


  • whenCore group programmersTasks on hand and tasks in reserve (workQueue) are atsaturated, will recruit oneThe intern(Non-core thread) to share the task

    After a task is submitted, if the number of threads in the thread pool reaches but does not exceed the maximum number of core threads, and the number of tasks in the task queue reaches the maximum number, a non-core thread is created to execute the task

We changed the number of tasks to 12, other values unchanged, so there will be two interns participating in the development (taskSize – (corePoolSize + queueSize) = 2)

private static int taskSize = 12;/ / number of jobs
private static int corePoolSize = 3;// Number of core threads
private static int maximumPoolSize = 10;// Maximum number of threads
private static int queueSize = 7;// The number of tasks that can be stored
Copy the code

See the following figure for the running results (additional interns 4 and 5)


  • What if the client asks for new requirements, but the core programmers and interns are not available? Boss: “that return use to ask? Overtime!”

Add… Working overtime??

The correct answer is to get rid of it! Refused to!

After a task is submitted, if the number of threads in the thread pool reaches the maximum, all threads are executing tasks, and the task queue is saturated, the task is rejected and the corresponding operation is performed based on the rejection policy

The previous example created five threads (3 core threads, 2 non-core threads), so this time we will just change the maximum number of threads to 4, using the default rejection policy

private static int taskSize = 12;/ / number of jobs
private static int corePoolSize = 3;// Number of core threads
private static int maximumPoolSize = 4;// Maximum number of threads
private static int queueSize = 7;// The number of tasks that can be stored

public static void main(String args[]){
	// omit some code...
	for (int i = 0; i < size; i++) {
		executor.execute(task);
		System.out.println("Got the call." + i);
	}
	executor.shutdown();
}
Copy the code

Run results below, the thread pool can be seen only received 11 mission (maximumPoolSize + queueSize = 11), after the 12th task was submitted will be thrown RejectedExecutionException exception

Also note that if we throw an exception when we submit the task, the subsequent shutdown() call will become invalid code and the thread pool will remain running in the main thread and cannot be closed


  • There is also a special case if the companyNot going to hire core programmers.The number of tasks received is smaller than the capacity of the task queueIn order to save money, the company will onlyHiring an internTo complete the development task

    If corePoolSize = 0, the thread pool will create only one non-core thread to execute the task if the number of tasks in the task queue has not reached the maximum after the task is submitted

private static int taskSize = 9;/ / number of jobs
private static int corePoolSize = 0;// Number of core threads
private static int maximumPoolSize = 5;// Maximum number of threads
private static int queueSize = 10;// The number of tasks that can be stored
Copy the code

The running result is shown in the following figure


Differences between blockingQueues of different types

BlockingQueue is an interface that provides three methods for adding elements:

  • Offer: Adds elements to the queue, returns true on success, false on failure
  • Add: Adds elements to the queue, returns true on success, throws IllegalStateException because capacity is full (add actually calls offer inside)
  • Put: Adds elements to a queue. If the queue is full, it blocks until it is full

There are three ways to delete elements:

  • Poll: Removes the header element of the queue or returns NULL if the queue is empty. Otherwise return elements
  • Remove: Finds the corresponding element based on the object and deletes it. Return true on success, false otherwise
  • Take: Removes the head element of the queue. If the queue is empty, block until the queue has an element and deletes it

All five types of queues are actually BlockingQueue implementation classes. This blog will not analyze the implementation of the source code, but will compare the differences in their use:

ArrayBlockingQueue

Based on an ArrayBlockingQueue, ArrayBlockingQueue internally maintains a buffer queue of arrays of a fixed size that needs to be specified at queue initialization. Elements stored in the ArrayBlockingQueue are accessed in a FIFO (first-in, first-out) manner. ArrayBlockingQueue is constructed as follows

ArrayBlockingQueue(int capacity)
ArrayBlockingQueue(int capacity, boolean fair)
ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)
Copy the code
  • Int capacity: indicates the capacity of the queue. The maximum number of elements can be stored
  • Boolean fair: Specifies an access policy. If true, access the blocking queue in FIFO order. If set to false, the access order is indeterminate
  • Collection
    c: Sets the initialization of collection elements to include and adds elements in the traversal order of collection iterators

We will only use the constructor of one parameter as an example. See the previous section for the task execution strategy and examples of its use


LinkedBlockingQueue

A linked list-based (one-way linked list) blocking queue, similar to an ArrayBlockingQueue, maintains a buffer queue of one-way linked lists. Unlike ArrayBlockingQueue, LinkedBlockingQueue can be initialized without setting a size. Its default size is Integer.MAX_VALUE (2 ^ 31 -1). If the size is set, it works like ArrayBlockingQueue. LinkedBlockingQueue is constructed as follows

LinkedBlockingQueue()
LinkedBlockingQueue(int capacity)
LinkedBlockingQueue(Collection<? extends E> c)
Copy the code

The parameters are the same as before, and the way you use it is pretty much the same as ArrayBlockingQueue


PriorityBlockingQueue

A priority-based blocking queue is similar to a LinkedBlockingQueue, except that the elements are not sorted in FIFO order, which is up to us to define: All object elements inserted into PriorityBlockingQueue must implement the Comparable interface. Our implementation of the Comparable interface defines queue priorities

PriorityBlockingQueue has an “unbounded” queue capacity, because new tasks that come in and are found to have exceeded the queue’s initial capacity are expanded. This means that if corePoolSize > 0, the number of threads in the pool reaches the maximum number of core threads, and the number of tasks in the task queue reaches the maximum number, a new task is submitted, and the pool does not create non-core threads to execute the new task, but rather expands the task queue

For more details, check out this blog post: Concurrent Queues — Unbounded blocking Priority Queues

PriorityBlockingQueue is constructed as follows

PriorityBlockingQueue()
PriorityBlockingQueue(int initialCapacity)
PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)
PriorityBlockingQueue(Collection<? extends E> c)
Copy the code

Duplicate parameters are not explained

  • Int initialCapacity: initialCapacity of the priority queue. Default is 11 (DEFAULT_INITIAL_CAPACITY)
  • Comparator
    comparator: comparator for priority queue sorting (comparator sorting). If this parameter is set to NULL, the queue elements are sorted using the same collation that we implemented the Comparable interface. Otherwise, the queue elements are sorted using the same collation defined in the Comparator. For more detailed comparison, you can refer to the information for more information.)

Let’s look at a concrete example (to see how it works in the simplest scenario) : We set the number of core programmers (corePoolSize) to 0 and the size of the task queue to the default (11). At this time, the company receives 12 development tasks. The project manager will sort the tasks according to their priority and then assign them to interns (the company can only recruit one intern, The reason for this is explained in parsing the task execution strategy.)

public class ExecutorTest {
    // omit some code...
    private static int taskSize = 9;/ / number of jobs
    private static int corePoolSize = 0;// Number of core threads
    private static int maximumPoolSize = 5;// Maximum number of threads
    private static int queueSize = 10;// The number of tasks that can be stored

    public static class PriorityTask implements Runnable.Comparable<PriorityTask>{
        private int priority;

        public PriorityTask(int priority) {
            this.priority = priority;
        }
        @Override
        public void run(a) {
            if (taskSize > 0) {
                try{
                    Thread.sleep(1000);// Simulate development time
                    System.out.println(getTime() + getName(Thread.currentThread().getName())
                            + "Complete a development task with number T" + (taskSize--) + ", priority is:" + priority
                    );
                }catch(Exception e){ e.printStackTrace(); }}}@Override
        public int compareTo(PriorityTask task) {
            if(this.priority == task.priority){
                return 0;
            }
            return this.priority<task.priority?1: -1;// Those with a higher priority are executed first}}public static void main(String args[]){
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(
                        corePoolSize,
                        maximumPoolSize,
                        1,
                        TimeUnit.SECONDS,
                        new PriorityBlockingQueue<Runnable>(queueSize)
                );
				
        Random random = new Random();
        PriorityTask task;
        int size = taskSize;
        for (int i = 0; i < size; i++) {
            int p = random.nextInt(100);
            task = new PriorityTask(p);
            executor.execute(task);
            System.out.println("Got the call." + i + ", priority is :"+ p); } executor.shutdown(); }}Copy the code

The running result is shown in the following figure


DelayQueue

DelayQueue is very similar to PriorityBlockingQueue in that it is “unbounded” (DelayQueue does not require an initial capacity) and is also sorted based on priority. Except that elements in the DelayQueue must implement the Delayed interface (inherited from the Comparable interface), We need to override the Delayed. GetDelay method to set the delay for the element release (performing the task) (the return value of the getDelay method is the hold time of the queue element before it is released, if 0 or a negative value is returned, it means that the element has expired and needs to be released, so we usually compare the completion time with the current system time). The DelayQueue is constructed as follows

DelayQueue()
DelayQueue(Collection<? extends E> c)
Copy the code

This time we will treat the delay time as the task development time, and the shorter the task development time, the higher the priority

public class ExecutorTest {
    // omit some code...
    private static int taskSize = 5;/ / number of jobs
    private static int corePoolSize = 0;// Number of core threads
    private static int maximumPoolSize = 5;// Maximum number of threads

    public static class DelayTask implements Runnable.Delayed{
        private long finishTime;
        private long delay;
        
        public DelayTask(long delay){
            this. delay= delay;
            finishTime = (delay + System.currentTimeMillis());// Calculate the completion time
        }
        @Override
        public void run(a) {
            if (taskSize > 0) {
                try{
                    System.out.println(getTime() + getName(Thread.currentThread().getName())
                            + "Complete a development task with number T" + (taskSize--) + ", time:" + delay/1000
                    );
                }catch(Exception e){ e.printStackTrace(); }}}@Override
        public long getDelay(@NonNull TimeUnit unit) {
            // Compare the completion time to the current time, <=0 indicates that the element is due to be released
            return (finishTime - System.currentTimeMillis());
        }
        @Override
        public int compareTo(@NonNull Delayed o) {
            DelayTask temp = (DelayTask) o;
            return temp.delay < this.delay?1: -1;// The shorter the delay time, the higher the priority}}public static void main(String args[]){
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(
                        corePoolSize,
                        maximumPoolSize,
                        1,
                        TimeUnit.SECONDS,
                        new DelayQueue()
                );
        Random random = new Random();
        DelayTask task;
        int size = taskSize;
        for (int i = 0; i < size; i++) {
            long d = 1000 + random.nextInt(10000);
            task = new DelayTask(d);
            executor.execute(task);
            System.out.println("Got the call." + i + ", estimated completion time is :" + d/1000); } executor.shutdown(); }}Copy the code

The result is as follows


SynchronousQueue

Synchronization based blocking queue, which is a very special queue because it has no internal data cache space and elements can only exist when it tries to fetch. That is to say, if there are no execution of the inserted element when subsequent operations, then the behavior of the insert will be blocked, if SynchronousQueue will be used in the thread pool, then this scenario will be thrown RejectedExecutionException anomalies. This may be a little convoluted, but we’re going to go through some examples to help you understand it, so let’s look at the constructor

SynchronousQueue()
SynchronousQueue(boolean fair)
Copy the code

Again, the parameters are the same as before, so let’s look at the example:

After SynchronousQueue is implemented, the task queue cannot store tasks. This means that if you get a new task and no one is available to work on it (programmers are already working on it, and the company has filled its fill), the new task will be aborted (throw an exception).

For example, we set the number of core programmers (corePoolSize) to 3, the total number of programmers (maximumPoolSize) to 9, and the number of tasks (taskSize) to 10

public class ExecutorTest {
    // omit some code...
    private static int taskSize = 10;/ / number of jobs
    private static int corePoolSize = 3;// Number of core threads
    private static int maximumPoolSize = 9;// Maximum number of threads

    public static void main(String args[]){
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(
                        corePoolSize,
                        maximumPoolSize,
                        1,
                        TimeUnit.SECONDS,
                        new SynchronousQueue<Runnable>()
                );
        TestTask task;
        int size = taskSize;
        for (int i = 0; i < size; i++) {
            task = new TestTask();
            executor.execute(task);
            System.out.println("Got the call."+ i); } executor.shutdown(); }}Copy the code

In a full hire, the company has a maximum of nine programmers, and when it gets to the tenth task and finds no one available, it throws an exception. Of course, previously successfully received tasks will not be affected

Therefore, based on the nature of SynchronousQueue, maximumPoolSize is usually set to “no boundaries”, that is, integer. MAX_VALUE (in the thread pool that the system preset for us, This is how CachedThreadPool is set up, more on that later.)


System default thread pool

All of this is about customizing a thread pool. In order to facilitate our development, the system has already encapsulated various thread pools for us to use. We can instantiate the thread pool we need by using Executors. NewXXX. There are many types of thread pools to choose from:

Let’s just pick four of the common ones. (The difference between the different thread pools is just the parameters that are passed in to build the pool. After our previous discussion of task execution strategies and the various task queues, understanding the different types of thread pools becomes very easy. That’s why the blogger gives you such a long example. I hope you can understand it.

CachedThreadPool

Let’s go straight to how is the system packaged

public static ExecutorService newCachedThreadPool(a) {
	return new ThreadPoolExecutor(
	                            0, 
	                            Integer.MAX_VALUE,
	                            60L, 
	                            TimeUnit.SECONDS,
	                            new SynchronousQueue<Runnable>()
	);
}
Copy the code

The core thread count is set to 0, the total number of threads is set to integer. MAX_VALUE, the idle time of threads is 60 seconds, and the task queue is SynchronousQueue.

  • CachedThreadPool has only non-core threads. After a task is submitted, if all existing threads (or the number of threads is 0) are executing the task, a new thread is created to execute the task
  • Idle threads are reclaimed after 60 seconds
  • All submitted tasks are executed immediately (because the task queue is SynchronousQueue)
  • CachedThreadPool can significantly improve program performance when performing a large number of asynchronous tasks with short life cycles

As an example, we set the number of tasks to 10

ExecutorService service = Executors.newCachedThreadPool();
TestTask task;
int size = taskSize;
for (int i = 0; i < size; i++) {
	task = new TestTask();
	service.execute(task);
}
service.shutdown();
Copy the code


FixedThreadPool

The source code is as follows

public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(
	                            nThreads, 
	                            nThreads,
	                            0L, 
	                            TimeUnit.MILLISECONDS,
	                            new LinkedBlockingQueue<Runnable>()
    );
}
Copy the code

The maximum number of threads is equal to the number of core threads, the idle time of threads is 0, and the task queue is LinkedBlockingQueue. Therefore, the characteristics of FixedThreadPool are as follows:

  • The number of threads is fixed, there are only core threads in the thread pool, and there is no timeout limit for core threads
  • There is no size limit on the task queue capacity
  • FixedThreadPool is suitable for scenarios that require fast responses

As an example, we set the number of tasks to 10 and the number of core threads to 5

ExecutorService service = Executors.newFixedThreadPool(corePoolSize);
TestTask task;
int size = taskSize;
for (int i = 0; i < size; i++) {
	task = new TestTask();
	service.execute(task);
}
service.shutdown();
Copy the code


SingleThreadExecutor

The source code is as follows

public static ExecutorService newSingleThreadExecutor(a) {
	return new FinalizableDelegatedExecutorService
		(new ThreadPoolExecutor(1.1.0L, 
	                                TimeUnit.MILLISECONDS,
	                                new LinkedBlockingQueue<Runnable>()));
}
Copy the code

The core number of threads to 1, the maximum number of threads is also 1, thread idle time to 0, task queue for LinkedBlockingQueue, and it’s SingleThreadExecutor FinalizableDelegatedExecutorService instance of a class, So SingleThreadExecutor has the following features:

  • There is only one core thread, and all tasks are done sequentially in the same thread
  • There is no size limit on the task queue capacity
  • If a single thread is aborted during execution due to some error, a new thread is created to replace it and perform subsequent tasks (unlike newFixedThreadPool(1), which cannot create a replacement thread if the thread encounters an error abort).
  • With SingleThreadExecutor we don’t have to deal with thread synchronization

As an example, we set the number of tasks to 10

ExecutorService service = Executors.newSingleThreadExecutor();
TestTask task;
int size = taskSize;
for (int i = 0; i < size; i++) {
	task = new TestTask();
	service.execute(task);
}
service.shutdown();
Copy the code


ScheduledThreadPool

The source code is as follows

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(
        corePoolSize, 
        Integer.MAX_VALUE,
        DEFAULT_KEEPALIVE_MILLIS, //DEFAULT_KEEPALIVE_MILLIS = 10L
        MILLISECONDS,
        new DelayedWorkQueue()
    );
}
Copy the code

The maximum number of core threads is integer. MAX_VALUE, the idle time of threads is 10 milliseconds, and the task queue is DelayedWorkQueue (very similar to DelayQueue). ScheduledThreadPool has the following characteristics:

  • The number of core threads is fixed, and the number of non-core threads is unlimited
  • Non-core threads are immediately reclaimed when idle
  • You can perform scheduled tasks and tasks with fixed periods

Use the sample as follows, we call ScheduledExecutorService. Submit delay start task schedule method, delay time is 3 seconds:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
Runnable runnable = new Runnable(){
	@Override
	public void run(a) {
		System.out.println("Start operation, time:"+ getTime()); }}; scheduledExecutorService.schedule(runnable,3,TimeUnit.SECONDS);
System.out.println("Submit task, time:" + getTime());
Copy the code

In addition, scheduleAtFixedRate, scheduleWithFixedDelay and other methods to submit a task, not one example


Some extra stuff

1. In addition to using execute method to submit tasks, we can also use submit method. The submit method submits a task that implements the Callable interface, so it has a return value

There are two ways to manually close a thread pool:

  • ShutDown () : Closing the thread pool does not affect the submitted tasks
  • ShutDownNow () : Attempts are made to terminate the thread executing the task after closing the thread pool

3. How can we reasonably estimate the thread pool size?

emmmm… Basically that’s it, the blogger has covered all the knowledge of thread pool as much as possible (except source code analysis, I will post a single chapter to analyze the source code at a later opportunity), if there are any omissions or suggestions feel free to leave a comment. Please give me a thumbs up if you think the blogger’s writing is good. Your support is my biggest motivation ~