The use and packaging of Android thread pool ThreadPoolExecutor (kotlin)

// 传统开启线程方式
Thread(Runnable {
     //to do异步请求

}).start()

1. Problems with creating threads using new Thread()    

1> If a Thread is created for each item in a list, a large number of Threads will be created if the list is large, resulting in memory jitter and frequent GC recycling. You know, the collection of GC is in the main thread, which will cause lag.

2> Too many threads cause each thread to compete for CPU execution rights, and frequent thread switching leads to a decrease in efficiency.

3> Every item of ListView slides out of the window, and the thread cannot be stopped or controlled.

 

2. Benefits of using thread pool

1. Reuse the already created good threads to avoid frequent GC caused by frequent creation

2. Control the number of concurrent threads, use system resources reasonably, and improve application performance

3. Can effectively control the execution of threads, such as timing execution, cancel execution, etc.

 

3. Create a thread pool ThreadPoolExecutor 7 parameters

The number of core threads in the corePoolSize   thread pool

maximumPoolSize The   maximum number of threads in the thread pool

KeepAliveTime is the timeout duration of non-core threads. When the idle time of non-core threads in the system exceeds keepAliveTime, they will be recycled. If the allowCoreThreadTimeOut property of ThreadPoolExecutor is set to true, this parameter also represents the timeout duration of the core thread

unit The unit of the third parameter, including nanoseconds, microseconds, milliseconds, seconds, minutes, hours, days, etc.

The task queue in the workQueue thread pool, which is mainly used to store tasks that have been submitted but not yet executed. The tasks stored here are submitted by the execute method of ThreadPoolExecutor.

threadFactory   provides the function of creating new threads for the thread pool, we generally use the default

Handler rejection strategy. When a thread cannot perform a new task (usually because the number of threads in the thread pool has reached the maximum or the thread pool is closed), by default, when the thread pool cannot handle a new thread, it will throw a RejectedExecutionException.
 

3. Thread pool ThreadPoolExecutor method

1> shutDown()   closes the thread pool without affecting the submitted tasks

2> shutDownNow()  closes the thread pool and tries to terminate the executing thread

3> allowCoreThreadTimeOut(boolean value)  allows core threads to be recycled when idle timeout

 

3. Thread pool package class



import com.orhanobut.logger.Logger
import java.util.concurrent.*

/***
 *      Created by LiangJingJie on 2019/5/16.
 *      线程池封装类
 * */
class ThreadPoolManager private constructor() {

    private var threadPoolMap = hashMapOf<String, ThreadPoolExecutor>()

    /**
     * cpu数量
     * */
    private val CPU_COUNT = Runtime.getRuntime().availableProcessors()

    /**
     * 核心线程数为手机CPU数量+1
     * */
    private val CORE_POOL_SIZE = CPU_COUNT + 1

    /**
     * 最大线程数为手机CPU数量×2+1
     * */
    private val MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1

    /**
     * 线程活跃时间 秒,超时线程会被回收
     * */
    private val KEEP_ALIVE_TIME: Long = 3

    /**
     * 等待队列大小
     * */
    private val QUEUE_SIZE = 128

    companion object {
        fun getInstance() = SingleHolder.SINGLE_HOLDER
    }

    object SingleHolder {
        val SINGLE_HOLDER = ThreadPoolManager()
    }


    /**
     *   @param tag 针对每个TAG 获取对应的线程池
     *   @param corePoolSize  线程池中核心线程的数量
     *   @param maximumPoolSize  线程池中最大线程数量
     *   @param keepAliveTime 非核心线程的超时时长,
     *   当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收
     *   如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长
     *   @param unit 第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等
     *   @param queueSize 等待队列的长度 一般128 (参考 AsyncTask)
     *   workQueue 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
     *   threadFactory  为线程池提供创建新线程的功能,这个我们一般使用默认即可
     *
     *   1.ArrayBlockingQueue:这个表示一个规定了大小的BlockingQueue,ArrayBlockingQueue的构造函数接受一个int类型的数据,
     *              该数据表示BlockingQueue的大小,存储在ArrayBlockingQueue中的元素按照FIFO(先进先出)的方式来进行存取。
     *   2.LinkedBlockingQueue:这个表示一个大小不确定的BlockingQueue,在LinkedBlockingQueue的构造方法中可以传
     *          一个int类型的数据,这样创建出来的LinkedBlockingQueue是有大小的,也可以不传,不传的话,
     *          LinkedBlockingQueue的大小就为Integer.MAX_VALUE
     * */
    private fun getThreadPool(tag: String): ThreadPoolExecutor {
        var threadPoolExecutor = threadPoolMap[tag]
        if (threadPoolExecutor == null) {
            threadPoolExecutor = ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAXIMUM_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                ArrayBlockingQueue<Runnable>(QUEUE_SIZE),
                Executors.defaultThreadFactory(),
                RejectedExecutionHandler { _, _ ->
                    Logger.d("$ThreadPoolManager  RejectedExecutionHandler----")
                }
            )
            //允许核心线程闲置超时时被回收
            threadPoolExecutor.allowCoreThreadTimeOut(true)
            threadPoolMap[tag] = threadPoolExecutor
        }
        return threadPoolExecutor
    }

    /**
     *  @param tag 针对每个TAG 获取对应的线程池
     *  @param runnable 对应的 runnable 任务
     * */
    fun removeTask(tag: String, runnable: Runnable) {
        getThreadPool(tag)?.queue?.remove(runnable)
    }

    /**
     *  @param tag 针对每个TAG 获取对应的线程池
     *  @param runnable 对应的 runnable 任务
     * */
    fun addTask(tag: String, runnable: Runnable) {
        getThreadPool(tag).execute(runnable)
    }

    /**
     *   @param tag 针对每个TAG 获取对应的线程池
     *   取消 移除线程池
     * */

    //shutDown():关闭线程池后不影响已经提交的任务
    //shutDownNow():关闭线程池后会尝试去终止正在执行任务的线程
    fun exitThreadPool(tag: String) {
        var threadPoolExecutor = threadPoolMap[tag]
        if (threadPoolExecutor != null) {
            threadPoolExecutor.shutdownNow()
            threadPoolMap.remove(tag)
        }
    }
}

 

The general idea of ​​the package class :

1> Use a hashMap to store each thread pool corresponding to TAG

2> Add thread task through addTask(tag: String, runnable: Runnable)

3> Remove thread task by removeTask(tag: String, runnable: Runnable)

      This should be explained first. The system API  ThreadPoolExecutor does not provide an interface to remove tasks. In the removeTask() method, the corresponding queue BlockingQueue is obtained through ThreadPoolExecutor , and the thread task Runnable is removed through the queue. Because this BlockingQueue is  the ArrayBlockingQueue passed in through the constructor when we create the  ThreadPoolExecutor instance, we will add tasks through the thread pool later, and they will all be put into this queue. Thread pool execution tasks will be taken out of this queue. Pro test is no problem. If you have any questions, please tell my brother

 

OK now this time we run to run the code

[ Scenario 1 The  number of core threads is greater than the number of tasks ]  

          We set the number of core threads CORE_POOL_SIZE to 20, the maximum number of threads MAXIMUM_POOL_SIZE to 30, and the waiting queue maintains 128

Then add 10 tasks. Obviously, the tasks are executed all at once.

// 创建十个任务
        for (i in 1..10) {
            val runnable = Runnable {
                val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms")
                val time = sdf.format(Date(java.lang.Long.parseLong(System.currentTimeMillis().toString())))
                Logger.d("ThreadPoolManager  Runnable ---- $i time: $time" )
            }
            ThreadPoolManager.getInstance().addTask("TAG", runnable)
        }

Look at the log and it is executed in sequence, look at the time, it is executed almost at the same time

 

[ Scenario 2 The  number of core threads is less than the number of tasks ]

We set the number of core threads CORE_POOL_SIZE to 3, and the maximum number of threads MAXIMUM_POOL_SIZE to 5, and the waiting queue maintains 128. In order to simulate the time-consuming operations performed by threads, each thread sleeps for 5 seconds after executing the function.

// 创建十个任务
        for (i in 1..10) {
            val runnable = Runnable {
                val sdf = SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒")
                val time = sdf.format(Date(java.lang.Long.parseLong(System.currentTimeMillis().toString())))
                Logger.d("ThreadPoolManager  Runnable ---- 任务$i   执行时间是: $time")
                //模拟耗时操作,睡眠5秒
                Thread.sleep(5000)
            }
            ThreadPoolManager.getInstance().addTask("TAG", runnable)
        }

 

Look at the printed results: the first 3 (1-3) will be executed first, after 5 seconds, then 3 (4-6) will be executed, after 5 seconds, then three (7-9) will be executed, and finally the 10th will be executed

 

Analyze the process of thread pool:

1> Because the number of tasks is 10, the maximum number of threads is 5, the core thread is only 3, and the waiting queue maintains 128.

2> So when adding the first 3 tasks to the task queue, 3 core threads will be created first to execute the first three tasks. So tasks 1, 2, and 3 will be executed first.

3> When the fourth task is added to the thread pool, because the core thread has reached the upper limit, but the task queue (maximum 128) has not been filled, so it will not start a new non-core thread to perform the task, but Put the task in the waiting queue and wait until there are idle core threads to execute the tasks in the waiting queue.

4> So it is the execution of 3 tasks and 3 tasks.

 

As for other scenarios, the following rules are followed:

1. After executing a thread, if the number of threads in the thread pool does not reach the number of core threads, a core thread will be immediately activated to execute

2. After executing a thread, if the number of threads in the thread pool has reached the number of core threads, and the workQueue is not full, the new thread is put into the workQueue to wait for execution

3. After executing a thread, if the number of threads in the thread pool has reached the number of core threads but not more than the number of non-core threads, and the workQueue is full, then start a non-core thread to perform the task

4. After executing a thread, if the number of threads in the thread pool has exceeded the number of non-core threads, refuse to execute the task

 

I will not show them one by one.

 

[ Scene 3  remove the submitted task ]

In this scenario, I submit a task to the thread pool, but want to cancel the task when it is not executed. This scenario is very common when each item of RecycleView slides quickly. When the item enters the window, an asynchronous task must be executed, but the item will quickly slide out of the screen before it is executed. At this time, the task can be removed from the queue of the thread pool. Stop talking nonsense and go to the simulation code.

We set the number of core threads CORE_POOL_SIZE to 3, and the maximum number of threads MAXIMUM_POOL_SIZE to 5, and the waiting queue maintains 128.

var runnable6: Runnable? = null
        var runnable7: Runnable? = null
        var runnable8: Runnable? = null
        var runnable9: Runnable? = null
        for (i in 1..10) {
            val runnable = Runnable {
                val sdf = SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒")
                val time = sdf.format(Date(java.lang.Long.parseLong(System.currentTimeMillis().toString())))
                Logger.d("ThreadPoolManager  Runnable ---- 任务$i   执行时间是: $time")
                //模拟耗时操作,睡眠5秒
                Thread.sleep(5000)
            }
            ThreadPoolManager.getInstance().addTask("TAG", runnable)
            if (i == 6) {
                runnable6 = runnable
            }
            if (i == 7) {
                runnable7 = runnable
            }
            if (i == 8) {
                runnable8 = runnable
            }
            if (i == 9) {
                runnable9 = runnable
            }
        }
        //添加后,立马移除
        ThreadPoolManager.getInstance().removeTask("TAG", runnable6!!)
        ThreadPoolManager.getInstance().removeTask("TAG", runnable7!!)
        ThreadPoolManager.getInstance().removeTask("TAG", runnable8!!)
        ThreadPoolManager.getInstance().removeTask("TAG", runnable9!!)

 

operation result:

 

Analyze the process of thread pool:

1> Because the number of tasks is 10, the maximum number of threads is 5, the core thread is only 3, and the waiting queue maintains 128.

2> So when adding the first 3 tasks to the task queue, 3 core threads will be created first to execute the first three tasks. So tasks 1, 2, and 3 will be executed first.

3> When adding the 4-10th task to the thread pool, put the task into the waiting queue and wait.

4> So through the current thread pool, get the corresponding queue queue, and then remove the task.

This removeTask() is implemented like this:

/**
     *  @param tag 针对每个TAG 获取对应的线程池
     *  @param runnable 对应的 runnable 任务
     * */
    fun removeTask(tag: String, runnable: Runnable) {
        getThreadPool(tag)?.queue?.remove(runnable)
    }

This queue is the ArrayBlockingQueue() passed in through the construction when we create the thread pool instance. This queue is used to store Runnable tasks waiting to be executed. Deleted through this queue will not be executed.

This source code can also be verified through the source code:

ThreadPoolExecutor的构造方法:

 

Obtained through queue

By positioning the code, it is obviously the same variable.

 

 

The above code is no problem in pro-testing. If there is a problem or something wrong, please leave a message thank you.

 

Guess you like

Origin blog.csdn.net/Leo_Liang_jie/article/details/90263072