Manage threads - thread pool

Why do I need a thread pool

  • The creation of threads will open up the private memory of local method stack, virtual machine stack, program counter and other threads. At the same time, the above three areas need to be destroyed. Therefore, frequent creation and consumption consume more system resources;
  • When the number of tasks is much larger than the number of tasks that the thread can handle, it can not refuse the task friendly.

Based on the above two shortcomings of threads, in order to solve such shortcomings, we introduce thread pool.

What is a thread pool

It is the way to use pooling technology to manage threads and use threads. Similar to a warehouse in life, when we need a tool, we can directly go to the warehouse to get it, put it back after use, and we can continue to use it next time. The thread pool is a "warehouse" for putting threads. It can be taken when necessary, put them back after use, and can be used next time. There is no need to create and destroy threads frequently. The task queue in the thread pool can manage and execute tasks well.

Advantages of thread pool

1. Frequent thread creation and consumption can be avoided.
2. It can better manage the number of threads and resources.
3. It has more functions, such as thread pool, which can execute scheduled tasks.
4. The thread pool can more friendly reject tasks that cannot be processed.

How thread pools are created

1. Create a fixed number of thread pools

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static void main(String[] args) {

        // Create a fixed number of thread pools
        ExecutorService executorService =
                Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            // Perform tasks
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Thread Name:" +
                            Thread.currentThread().getName());
                }
            });
        }
    }
}

This method does not create 10 threads at the beginning of initialization, but according to your task volume. If your task can be completed by two threads, he will only create two threads. When your task volume increases, these two threads are not enough to create threads, but only 10 threads can be created at most.
Of course, you can also customize the thread pool rules (such as the name and priority of the thread pool...):

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // Custom thread factory
        MyThreadFactory threadFactory = new MyThreadFactory();
        ExecutorService executorService =
                Executors.newFixedThreadPool(10, threadFactory);
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    Thread thread = Thread.currentThread();
                    System.out.println("Thread Name:" +
                            thread.getName() +
                            ",Priority:" + thread.getPriority());
                }
            });
        }
    }

    private static int count = 1;

    static class MyThreadFactory implements ThreadFactory {

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            // Custom thread pool name rule
            thread.setName("mythreadpool-" + count++);
            // set priority
            thread.setPriority(10);
            return thread;
        }
    }

}

2. Create thread pool with cache

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // Create thread pool with cache
        ExecutorService executorService =
                Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Thread Name:" +
                            Thread.currentThread().getName());
                }
            });
        }
    }
}

In this way, the number of threads created will be determined according to the amount of tasks. For example, if there are 100 tasks, almost 30 threads will be created, because when executing the subsequent tasks, the threads executing the previous tasks may have finished executing, but they can continue to execute the remaining tasks, so there is no need to create more threads. Usage scenario: use newCachedThreadPool when there are a large number of tasks in a short time
3. Create a thread pool that can perform scheduled tasks

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // Create a thread pool to perform scheduled tasks
        ScheduledExecutorService scheduledExecutorService =
                Executors.newScheduledThreadPool(1);
        System.out.println("Set scheduled tasks:" + new Date());
      // Perform scheduled tasks
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                   e.printStackTrace();
               }
                System.out.println("Perform tasks:" + new Date());
            }
        }, 1, 3, TimeUnit.SECONDS);
//Parameter 1: task Runnable executed by thread
//Parameter 2: delay execution for a period of time
//Parameter 3: frequency of scheduled task execution
//Parameter 4: used in conjunction with parameter 2 and parameter 3. It specifies the unit of time


//        scheduledExecutorService.schedule(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("execute task:" + new Date());
//            }
//        }, 1, TimeUnit.SECONDS);
//It will only be executed once

        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Perform tasks:" + new Date());
            }
        }, 1, 3, TimeUnit.SECONDS);

    }
}

scheduleAtFixedRate: take the start time of the last task as the start time of the next task. schcdulcWithFixcdDelay: take the end time of the last task as the start time of the next task.

4. Create a thread pool for single thread execution of scheduled tasks

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // Create a single thread pool that performs scheduled tasks
        ScheduledExecutorService service =
                Executors.newSingleThreadScheduledExecutor();
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Perform tasks:" + new Date());
            }
        }, 1, 3, TimeUnit.SECONDS);

    }
}

5. Create a thread pool for a single thread

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // Create a thread pool for a single thread
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Thread Name:" +
                            Thread.currentThread().getName() +
                            ",I=" + finalI);
                }
            });
        }

    }
}

What is the use of creating a thread pool for a single thread?
1. It can avoid the performance overhead caused by frequent thread creation and consumption;
2. There is a task queue to store redundant tasks;
3. When a large number of tasks cannot be handled, the rejection strategy can be implemented friendly;
4. Thread pool can better manage tasks.
5. A single thread pool also has a task queue, so it can execute and manage more tasks.

6. Create a thread pool that is produced asynchronously according to the current CPU

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // Create a thread pool that is produced asynchronously according to the current CPU
        ExecutorService service = Executors.newWorkStealingPool();
        for (int i = 0; i < 10; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Thread Name:" +
                            Thread.currentThread().getName());
                }
            });
        }

        // Wait for thread pool execution to complete
        while (!service.isTerminated()) {
        }

    }
}

Possible problems in creating the first 6 thread pools:
1. Uncontrollable number of threads - > oom
2. Uncontrollable work task queue - > oom.

7. How the original thread pool was created (important)
Parameters of ThreadPoolExecutor

  • Parameter l: number of core threads | number of thread pools under normal conditions | number of regular employees

  • Parameter 2: maximum number of threads "the maximum number of threads (temporary workers) that can be created when there are a large number of tasks. The maximum number of threads must be greater than or equal to the number of core threads.

  • Parameter 3: maximum thread survival time (survival time of temporary workers)

  • Parameter 4: used with parameter 3, time unit

  • Parameter 5: task queue (first in first out, first in first service)

  • Parameter 6: thread factory parameter

  • 7: Reject strategy

public class ThreadPoolDemo {
    private static int count = 1;

    public static void main(String[] args) {

        // Create thread factory
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("myThreadPool-" + count++);
                return thread;
            }
        };

        // Original way of creating thread pool
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(2, 2,
                        60, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(1),
                        threadFactory);
        for (int i = 0; i < 3; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Thread Name:" +
                            Thread.currentThread().getName());
                }
            });
        }
    }
}

The current maximum number of tasks is equal to the maximum number of threads plus the number of tasks that can be stored in the task queue. When the total number of tasks is greater than the sum of the two, the rejection policy will be executed (the tasks in the task queue are omitted)

Execution flow of thread pool

1. When the number of tasks is less than the number of core threads, it will create a thread to execute this task;
2. When the number of tasks is greater than the number of core threads, and there are no idle threads, and when the number of threads in the thread pool is less than the maximum number of threads, the task will be saved to the task queue; (Note: because the cost of storing the extra tasks in the task queue is the smallest, the thread pool will store the new tasks in the task queue instead of creating a new thread to execute the tasks)
3. When the current number of tasks is relatively large, there are no idle threads and the task queue is full. At this time, it will judge whether the number of tasks in the current thread pool is greater than or equal to the maximum number of threads. If the number of threads in the current thread pool is less than the maximum number of threads, create threads to execute tasks;
4. If the number of threads in the current thread pool is equal to the maximum number of threads and the task queue is full, the reject policy will be executed.
You can see the following figure to understand

Of course, this is a theoretical case. In practice, the execution time of the task is uncertain. For example, when you put the task slowly, it is possible that when you put the next task, the previous task has been processed. At this time, the previous thread can be reused and continue to execute the new task, so that the rejection policy will not be executed and the task can be completed on time.
Thread pool termination:

//End thread pool
threadPoolExecutor.shutdown();

//Terminate the thread pool immediately (the tasks of the thread pool will not be completed)
threadPoolExecutor.shutdownNow( );

There are two ways of thread pool execution:
1. No return value of execute (New runnable...)
2. The execution task has a return value sumbit(Runnbale has no return value / Callable has a return value)

public class ThreadPoolDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(100));
        // Task execution mode 1
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("implement execute method");
            }
        });
        // Perform a task with a return value
        Future<Integer> future = threadPoolExecutor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                // Generate random number
                int num = new Random().nextInt(10);
                System.out.println("implement submit Method, random number:" + num);
                return num;
            }
        });
        System.out.println("Get thread pool execution results:" + future.get());
        // Use submit to execute Runnable task
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("implement submit Method, using Runnable object");
            }
        });
        threadPoolExecutor.shutdown();
    }
}

Status of thread pool

  • RUNNING: the initial state after the thread pool is created. In this state, tasks can be executed.
  • SHUTDOWN: in this state, the process pool will no longer accept new tasks, but will end the execution of tasks in the work queue.
  • STOP: in this state, the thread pool will no longer accept new tasks, nor process tasks in the work queue, and the thread will be interrupted.
  • TIDYING: in this state, all tasks have been terminated (cleared), and the terminated() method will be executed.
  • terminated: destroyed after executing the terminated () method.

Keywords: Java

Added by jemgames on Sat, 29 Jan 2022 05:12:28 +0200