美文网首页
Java 线程池的基本使用

Java 线程池的基本使用

作者: Rimson | 来源:发表于2018-12-08 20:23 被阅读0次

    一、为什么使用线程池

    在Android开发中,经常需要用到多线程异步操作,如果使用new Thread().start,会造成一些问题:

    1. 每次new Thread,新建对象性能差。
    2. 在任务众多的情况下,系统要为每一个任务创建一个线程,不销毁的话过多线程会造成OOM。
    3. 如果任务执行完毕后销毁每一个线程,又会造成线程频繁地创建与销毁,这会频繁地调用GC机制,这会使性能降低,又非常耗时。
    4. 缺乏更多功能,如定时执行、定期执行、线程中断。

    使用线程池可以带来的好处:

    1. 对多个线程进行统一地管理,避免资源竞争中出现的问题。
    2. 对线程进行复用,线程在执行完某个任务之后,可以执行另一个任务。

    二、几种常见的线程池

    1. ThreadPoolExecutor 最基本线程池

    后面几种线程池可以视为这个基本线程池的扩展和详细使用
    创建方式如下:

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

    corePoolSize

    核心线程数,默认情况下,即使核心线程处于闲置状态,它们也会一直存活。其有一个allowCoreThreadTimeOut属性,如果设置为true,那么核心线程池会有超时策略。超时的时长为第三个参数 keepAliveTime。如果超时,核心线程会被终结。

    maximumPoolSize

    最大线程数,当线程数量达到这个最大值时,后续添加的新任务会被阻塞。

    keepAliveTime

    非核心线程闲置的超时时长,超过这个时长,非核心线程会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime对核心线程同样有效。

    unit

    用于指定keepTimeAlive的时间单位,如TimeUnit.MILLISECONDS、TimeUnit.SECONDS等。

    workQueue

    线程中的任务队列,通过线程池的execute方法提交的Runnable对象会储存在这个队列中。

    threadFactory(不常用)

    线程工厂,提供创建新线程的功能。ThreadFactory是一个借口,只有一个方法:Thread newThread(Runnable r),可以用于设置线程名字。

    handler(不常用)

    当线程池无法执行新任务时,可能是因为队列已满,或者执行失败,这时会调用handler的rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法来抛出异常。

    执行规则:

    1. 如果线程未达到核心线程数量,那么直接启动一个核心线程。
    2. 如果线程达到核心线程的数量,任务会被插入到任务队列(workQueue)排队。
    3. 如果任务队列已满导致步骤2无法插入到任务队列,那么开启一个非核心线程执行。
    4. 如果步骤3的线程数量达到线程池规定数目(maxmumPoolSize),那么拒绝执行此任务。

    demo

            val threadPoolExecutor = ThreadPoolExecutor(3, 5, 1, TimeUnit.SECONDS, LinkedBlockingQueue<Runnable>(100))
            base_button.setOnClickListener {
                var i = 0
                while (i < 30) {
                    val final = i
                    val runnable = Runnable {
                        Thread.sleep(2000)
                        Log.d("Thread", "run:$final")
                    }
                    threadPoolExecutor.execute(runnable)
                    i++
                }
            }
    

    结果会每隔2s,打印3个日志,日志内容为连续数
    小问题:日志会按顺序打印吗?每次循环都有sleep,为什么可以做到每隔2s打印3个日志?

    2、FixedThreadPool (可重用固定线程数)

    参数为核心线程数,只有核心线程,无非核心线程,无超时机制,阻塞队列无界。
    当线程处于空闲状态时,只要线程池不被关闭它们就并不会被回收。当所有线程都处于活动状态,新任务就会处于等待状态,直到有线程空闲出来。适用于执行长期任务。

    demo

            val fixedThreadPoolExecutor = Executors.newFixedThreadPool(5)
            var i = 0
            while (i < 30) {
                val final = i
                val runnable = Runnable {
                    Thread.sleep(2000)
                    Log.d("Thread", "run:$final")
                }
                fixedThreadPoolExecutor.execute(runnable)
                i++
            }
    

    结果为每2s打印5次日志

    3、CachedThreadPool (按需创建)

    线程数量不定,没有核心线程,只有非核心线程,每个线程空闲等待的时间为60s,采用SynchronousQueue队列。当线程都处于活动状态时,线程池会创建新线程来执行任务,否则就会复用空闲的线程。SynchronousQueue可以理解成一个无法储存元素的队列,因为其中的任务会被马上执行。这种线程池适用于大量耗时少的任务。当所有线程都处于闲置状态,线程会逐渐因为超时被停止,线程池中就没有线程,几乎不占系统资源。

    demo

                val cachedThreadPoolExecutor = Executors.newCachedThreadPool()
                var i = 0
                while (i < 30) {
                    val final = i
                    val runnable = Runnable {
                        Thread.sleep(2000)
                        Log.d("Thread", "run:$final")
                    }
                    cachedThreadPoolExecutor.execute(runnable)
                    i++
                }
    

    结果为2s之后打印30次日志

    4、ScheduledThreadPool(定时延时执行)

    核心线程数固定,非核心线程数没有限制,非核心线程闲置时会被立即回收。主要用于执行定时任务和周期性重复任务。

    demo

            val scheduledThreadPoolExecutor = Executors.newScheduledThreadPool(3)
            val runnable = Runnable {
                Log.d("Thread", "This task is delayed to execute")
            }
            scheduled_button1.setOnClickListener {
                // 延迟3s后启动任务,只执行一次
                scheduledThreadPoolExecutor.schedule(runnable, 3, TimeUnit.SECONDS)
            }
            scheduled_button2.setOnClickListener {
                // 延迟3s后启动,每隔1s执行一次
                scheduledThreadPoolExecutor.scheduleAtFixedRate(runnable, 3, 1, TimeUnit.SECONDS)
            }
            scheduled_button3.setOnClickListener {
                // 第一次延迟3s后执行,之后每隔1s执行一次
                scheduledThreadPoolExecutor.scheduleWithFixedDelay(runnable, 3, 1 ,TimeUnit.SECONDS)
            }
    

    运行结果如注释所示

    5、SingleThreadExecutor(单核fixed)

    只有一个核心线程,存活时间无限

    demo

                val singleThreadPoolExecutor = Executors.newSingleThreadExecutor()
                var i = 0
                while (i < 30) {
                    val final = i
                    val runnable = Runnable {
                        Thread.sleep(2000)
                        Log.d("Thread", "run:$final")
                    }
                    singleThreadPoolExecutor.execute(runnable)
                    i++
                }
    

    结果为每隔2s打印一次日志,因为只有一个核心线程,当该线程被占用时,其他任务需要等待。

    三、线程池的其他方法

    1.shutDown()  关闭线程池,不影响已经提交的任务
    
    2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程
    
    3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
    
    4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值。
    
    5.beforeExecute()  任务执行前执行的方法
    
    6.afterExecute() 任务执行结束后执行的方法
    
    7.terminated()  线程池关闭后执行的方法
    

    相关文章

      网友评论

          本文标题:Java 线程池的基本使用

          本文链接:https://www.haomeiwen.com/subject/gwuqhqtx.html