Java 线程池的基本使用

2018-12-08  本文已影响0人  Rimson

一、为什么使用线程池

在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()  线程池关闭后执行的方法
上一篇下一篇

猜你喜欢

热点阅读