Java multi-threaded get to know a thread pool

Use Java multi-threaded thread pool

1. Introduction thread pool

1.1 What is the thread pool

Thread pool, in essence, is an object pool for managing the thread resources. Before the implementation of the task, it is necessary to come up with the thread from the thread pool for execution. After completion of task execution, the need to thread back into the thread pool .
Through this mechanism repeated use of threads, can effectively avoid the harm created directly caused by the thread.

1.2 Benefits thread pool

(1) reduce system resource consumption by reusing existing thread, reduce thread creation and consumption of the destruction caused;
(2) to improve the response speed of the system , when a task arrives, by reusing existing threads, without creating new the thread can be executed immediately;
(3) easy to control the number of concurrent threads . Because the creation of threads if unrestricted, may lead to excessive memory footprint generated OOM (out of memory), and can cause excessive switching cpu (cpu thread switching is a time cost (need to keep the site current execution thread and resume to site execution threads)).
(4) provide more powerful features, delay timer thread pool.

2. The principle

2.1 flowchart thread pool

Here Insert Picture Description
General process can be summarized as follows:
(1) first determine whether the core of the thread pool is full, if not, create a thread to perform tasks
(2) If the core thread pool is full, whether the queue is full, if the queue is not full, put the task in the queue
(3) If the queue is full, it is determined whether the thread pool is full, if not full, create a thread to perform the task
(4) If the thread pool is also full, then refused to follow the policy of processing tasks

2.2 ThreadPoolExecutor process flow

Here Insert Picture Description
Difficult:
1, why you need to use the thread pool (blocking) queue?

(1) Because a thread is created if unrestricted, it may lead to excessive memory footprint generated OOM, and can cause excessive cpu switch.
(2) create a thread pool thread needs to get mainlock this global lock, the impact of concurrent efficiency, blocking queue can be a good buffer

2. Why should I use a thread pool instead of using non-blocking blocking queue queue?

(1) blocking queue can not guarantee a task queue task (that is, the queue is empty) blocked thread gets the task , making the thread into the wait state, the release of cpu resources.
(2) When there is only the task queue wakeup message corresponding to the thread removed from the queue for execution .

3. Create a common thread pool

3.1 Executors simply create four kinds of thread pool

(1) SingleThreadPool () to create a single thread pool thread

This thread pool is only a thread . If multiple tasks are submitted to this thread pool, it will be cached in the queue (queue length Integer.MAX_VALUE ). When the thread is idle, according to FIFO processing approach.

Illustration:
Here Insert Picture Description
Example:

package ThreadPool;

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

public class SignalThreadPoolTest {
    public static void main(String args[]){
        //创建单线程的线程池
        ExecutorService singal = Executors.newSingleThreadExecutor();

        for(int i = 0; i < 5; ++i){
            int task = i;
            singal.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程:" + Thread.currentThread().getName() + "正在执行" + task);
                }
            });
        }
        System.out.println("五个任务开始执行!");
    }
}

operation result:

五个任务开始执行!
线程:pool-1-thread-1正在执行0
线程:pool-1-thread-1正在执行1
线程:pool-1-thread-1正在执行2
线程:pool-1-thread-1正在执行3
线程:pool-1-thread-1正在执行4

从结果可以看出,程序始终只创建了一个线程,而且线程的执行是按FIFO顺序执行的。

(2)FixedThreadPool()创建固定数量的线程池

和创建单一线程的线程池类似,只是这儿可以并行处理任务的线程数更多一些罢了。若多个任务被提交到此线程池,会有下面的处理过程。

图解:
Here Insert Picture Description
实例:

package ThreadPool;

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

public class SignalThreadPoolTest {
    public static void main(String args[]){
        //创建单线程的线程池
        ExecutorService threadExecutor = Executors.newFixedThreadPool(2);

        for(int i = 0; i < 5; ++i){
            int task = i;
            threadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程:" + Thread.currentThread().getName() + "---------正在执行" + task);
                }
            });
        }
        System.out.println("五个任务开始执行!");
    }
}

运行结果:

五个任务开始执行!
线程:pool-1-thread-1---------正在执行0
线程:pool-1-thread-2---------正在执行1
线程:pool-1-thread-1---------正在执行2
线程:pool-1-thread-2---------正在执行3
线程:pool-1-thread-1---------正在执行4

(3)CachedThreadPool 创建带缓存的线程池

其核心线程池的长度为0,线程池最大长度为Integer.MAX_VALUE。由于本身使用SynchronousQueue作为等待队列的缘故,导致往队列里面每插入一个元素,必须等待另一个线程从这个队列删除一个元素。

图解:
Here Insert Picture Description
该类型的线程池经常用于web服务器处理大量的请求。

(4)ScheduledThreadPool 创建定时调度的线程池

该线程池可以实现定时调度的功能

1、scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),定时调度,每个调度任务会至少等待period的时间,如果任务执行的时间超过period,则等待的时间为任务执行的时间

2、scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit),定时调度,第二个任务执行的时间 = 第一个任务执行时间 + delay

3、schedule(Runnable command, long delay, TimeUnit unit),定时调度,延迟delay后执行,且只执行一次

实例:

package ThreadPool;

        import java.util.concurrent.Executors;
        import java.util.concurrent.ScheduledExecutorService;
        import java.util.concurrent.TimeUnit;

public class SignalThreadPoolTest {
    public static void main(String args[]){
        //创建单线程的线程池
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

        // 定时调度,每个调度任务会至少等待`period`的时间,
        // 如果任务执行的时间超过`period`,则等待的时间为任务执行的时间
        executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10000);
                    System.out.println("11111---"  + System.currentTimeMillis()/1000);
                    System.out.println("-----------1111--------------");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },0, 2, TimeUnit.SECONDS);

        // 定时调度,第二个任务执行的时间 = 第一个任务执行时间 + `delay`
        executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                    System.out.println("2222---"  + System.currentTimeMillis()/1000);
                    System.out.println("-------------2222-------------");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 0, 2, TimeUnit.SECONDS);

        // 定时调度,延迟`delay`后执行,且只执行一次
        executor.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("5 秒之后执行 schedule");
            }
        }, 5, TimeUnit.SECONDS);
    }
}

结果:

-------------2222-------------
5 秒之后执行 schedule
11111---1583070844
-----------1111--------------
2222---1583070846
-------------2222-------------
2222---1583070853
-------------2222-------------
11111---1583070854
-----------1111--------------
----
----

从结果大致可以看出:
2号线程:满足 第二个任务执行的时间 = 第一个任务执行时间 + delay
1号线程:满足:如果任务执行的时间超过period,则等待的时间为任务执行的时间

3.2 ThreadPoolExecutor手动创建线程池

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

参数解析:
(1)corePoolSize:线程池中的核心线程数;
(2)maximumPoolSize:线程池中的最大线程数;
(3)keepAliveTime:空闲时间,当线程池数量超过核心线程数时,多余的空闲线程存活的时间,即:这些线程多久被销毁。
(4)unit:空闲时间的单位,可以是毫秒、秒、分钟、小时和天,等等
(5)workQueue:等待队列,线程池中的线程数超过核心线程数时,任务将放在等待队列,它是一个BlockingQueue类型的对象
(6)threadFactory:线程工厂,我们可以使用它来创建一个线程
(7)handler:拒绝策略,当线程池和等待队列都满了之后,需要通过该对象的回调函数进行回调处理

重点关心workQueue,threadFactory,handle这几个参数

1、等待队列-workQueue
等待队列是BlockingQueue类型的,理论上只要是它的子类,我们都可以用来作为等待队列。
(1)ArrayBlockingQueue,队列是有界的,基于数组实现的阻塞队列
(2)LinkedBlockingQueue,队列可以有界,也可以无界。基于链表实现的阻塞队列
(3)SynchronousQueue,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将一直处于阻塞状态。该队列也是Executors.newCachedThreadPool()的默认队列
(4)PriorityBlockingQueue,带优先级的无界阻塞队列

2、线程工厂-threadFactory

Executors的实现使用了默认的线程工厂-DefaultThreadFactory。它的实现主要用于创建一个线程,线程的名字为pool-{poolNum}-thread-{threadNum}。

3、拒绝策略-handler

(1)CallerRunsPolicy // 在调用者线程执行
(2)AbortPolicy // 直接抛出RejectedExecutionException异常
(3)DiscardPolicy // 任务直接丢弃,不做任何处理
(4)DiscardOldestPolicy // 丢弃队列里最旧的那个任务,再尝试执行当前任务

4、 提交任务的几种方式

1、execute(),执行一个任务,没有返回值。

2、submit(),提交一个线程任务,有返回值。
(1)submit(Callable task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。

(2)submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。
(3)submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null。

Future.get方法会使取结果的线程进入阻塞状态,知道线程执行完成之后,唤醒取结果的线程,然后返回结果。

5、线程池的关闭

(1)shutdown()会将线程池状态置为SHUTDOWN,不再接受新的任务,同时会等待线程池中已有的任务执行完成再结束。

(2)shutdownNow()会将线程池状态置为SHUTDOWN,对所有线程执行interrupt()操作,清空队列,并将队列中的任务返回回来。

另外,关闭线程池涉及到两个返回boolean的方法,isShutdown()和isTerminated,分别表示是否关闭和是否终止。

6、如何配置线程池

(1)CPU密集型任务
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

(2)IO密集型任务
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

(3) mixed tasks
can be divided into IO intensive tasks and CPU-intensive tasks, and then were treated with different to the thread pool. After completion of the execution time of two minutes as long as the task less the same, so it will be efficient to perform than serial.
Because the two task execution time gap after the data level if divided, then the split does not make sense.
Because before executing the task of executing the mission would have to wait after the final time still depends on the task After you perform, but also to add task split and merger costs, outweigh the benefits.


Reference blog links:
https://www.jianshu.com/p/7ab4ae9443b9

Published 30 original articles · won praise 19 · views 3894

Guess you like

Origin blog.csdn.net/qqq3117004957/article/details/104597767