Java线程池使用

Android开发过程线程的使用很常见,最常见的用法应该是如下所示new一个线程。

    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
        }
    }
    new Thread().start();

这样使用确实很简单方便直观。

但如果线程的数量很多的话,这样会出现大量的线程创建和销毁,不仅浪费大量的内存,还会降低系统的运行效率。

为了能够复用线程,避免大量的内存浪费,我们可以使用线程池来创建和使用线程。

一、线程池ThreadPoolExecutor基本使用。

首先声明一个线程池对象,如下图可以看到有四个构造方法。不过追究到底发现其它三个方法都是第四个方法的实现,所以我们直接看第四个构造方法就行了。
构造方法
构造方法如下:

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

从上面代码可以看到,构造方法中做了一些非空的判断,和初始化赋值。下面来看一下各个参数的含义:

  • corePoolSize: 核心线程的大小。默认情况下,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,则不再创建,会把到达的任务放到缓存队列当中。除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,初始化时候会直接创建corePoolSize个线程。
  • maximumPoolSize :最大线程数,表示线程池中最多可以创建多少个线程
  • keepAliveTime:当线程数大于corePoolSize时,线程空闲后,保持存活的时间。如果线程数不超过corePoolSize,则keepAliveTime不起作用。除非调用了allowCoreThreadTimeOut(boolean)方法。
  • unit: keepAliveTime 的时间单位
  • workQueue: 缓冲队列,用来存储等待执行的任务。有以下几种:
    • LinkedBlockingQueue
    • SynchronousQueue
    • DelayedWorkQueue
  • threadFactory:线程工厂,负责创建工厂
  • handler:饱和策略,当到达线程数上限或工作队列已满时的拒绝处理逻辑。有以下四种策略。
    饱和策略
    • AbortPolicy:不处理,直接抛出异常。
    • CallerRunsPolicy:将任务分给调用线程来执行
    • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
    • DiscardPolicy:直接丢弃,不处理

分析完构造参数的含义,现在我们开始声明一个线程池,并且简单实用一下吧。

    public static int corePoolSize = 3;
    public static int maxPoolSize = 5;
    public static long keepAliveTime = 60;
    private static int mCount = 0;

    public static void main(String[] args) {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 20; i++) {
            threadPoolExecutor.execute(mRunnable);
        }
    }
    
    private static Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            mCount++;
            System.out.println("name:"+Thread.currentThread().getName() + "    count = " + mCount);
        }
    };

输入结果:

name:pool-1-thread-1    count = 1
name:pool-1-thread-2    count = 2
name:pool-1-thread-1    count = 3
name:pool-1-thread-2    count = 4
name:pool-1-thread-1    count = 5
name:pool-1-thread-2    count = 6
name:pool-1-thread-1    count = 7
name:pool-1-thread-2    count = 8
name:pool-1-thread-1    count = 9
name:pool-1-thread-2    count = 10
name:pool-1-thread-1    count = 11
name:pool-1-thread-2    count = 12
name:pool-1-thread-1    count = 13
name:pool-1-thread-2    count = 14
name:pool-1-thread-1    count = 15
name:pool-1-thread-2    count = 16
name:pool-1-thread-1    count = 17
name:pool-1-thread-2    count = 18
name:pool-1-thread-1    count = 19
name:pool-1-thread-3    count = 20

从输出结果可以看出,执行20次线程任务,使用了3个线程完成任务。不需要创建20个线程,大大节约了内存。

为了方便我们使用线程池,Java JDK提供了4中简单的创建方式。

二、线程池的四种创建方式

  • Executors.newCachedThreadPool();
  • Executors.newFixedThreadPool();
  • Executors.newSingleThreadExecutor();
  • Executors.newScheduledThreadPool();

1、newCachedThreadPool()

创建一个带缓存的线程池,如果有缓存线程则复用缓存线程,没有则创建新的线程。

构造方法如下:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  • 核心线程数corePoolSize = 0,
  • 最大线程数为 maximumPoolSize 为 int 的最大值,相当于无限大,
  • 保活时间keepAliveTime = 60秒
  • 阻塞队列workQueue使用的是SynchronousQueue 同步队列
  • defaultHandler是一个AbortPolicy

使用:

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            if (i < 10)
                Thread.sleep(500);
            service.execute(mRunnable);
        }
    }

当 i 小于10的时候,每次循环让线程sleep 500ms。执行效果如下:

name:pool-1-thread-1    count = 1
name:pool-1-thread-1    count = 2
name:pool-1-thread-1    count = 3
name:pool-1-thread-1    count = 4
name:pool-1-thread-1    count = 5
name:pool-1-thread-1    count = 6
name:pool-1-thread-1    count = 7
name:pool-1-thread-1    count = 8
name:pool-1-thread-1    count = 9
name:pool-1-thread-1    count = 10
name:pool-1-thread-1    count = 11
name:pool-1-thread-2    count = 12
name:pool-1-thread-1    count = 13
name:pool-1-thread-2    count = 14
name:pool-1-thread-3    count = 15
name:pool-1-thread-1    count = 17
name:pool-1-thread-2    count = 16
name:pool-1-thread-4    count = 18
name:pool-1-thread-5    count = 19
name:pool-1-thread-6    count = 20

可以看到,前面10个循环因为加了500毫秒的暂停让线程执行完成,所以再次执行则会复用已经创建好的空闲线程,并没有重新创建新的线程。后面的循环则一直创建了新的线程,因为没有空闲线程。

使用场景:

因为newCachedThreadPool方式创建的线程池核心线程数为0,最大线程数几乎无穷大。所以newCachedThreadPool适合处理需要创建大量耗时任务的请求。

2、newFixedThreadPool()

创建指定长度的线程池,线程数量超过指定长度则会在队列中等待执行。

构造方法

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

从构造方法可以看出,核心线程数corePoolSize 和最大线程数maximumPoolSize是相等的。并且保活时间是0ms。使用的是LinkedBlockingQueue缓冲队列。

使用:

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 20; i++) {
            if (i<10)
                Thread.sleep(500);
            service.execute(mRunnable);
        }
    }

运行结果:

name:pool-1-thread-1    count = 1
name:pool-1-thread-2    count = 2
name:pool-1-thread-3    count = 3
name:pool-1-thread-1    count = 4
name:pool-1-thread-2    count = 5
name:pool-1-thread-3    count = 6
name:pool-1-thread-1    count = 7
name:pool-1-thread-2    count = 8
name:pool-1-thread-3    count = 9
name:pool-1-thread-1    count = 10
name:pool-1-thread-1    count = 12
name:pool-1-thread-3    count = 13
name:pool-1-thread-2    count = 12
name:pool-1-thread-3    count = 15
name:pool-1-thread-1    count = 14
name:pool-1-thread-1    count = 18
name:pool-1-thread-1    count = 19
name:pool-1-thread-1    count = 20
name:pool-1-thread-3    count = 17
name:pool-1-thread-2    count = 16

我们指定线程数为3,前10个循环让线程sleep 500ms,可以看出newFixedThreadPool会创建新的线程来执行任务。

使用场景:

控制线程中最大并发数,超过最大并发时,等待执行

3、newSingleThreadExecutor

创建一个线程的线程池,线程池中只有一个线程,超过一个则等待执行

构造方法:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

从构造方法可以看见 核心线程数corePoolSize和最大线程数maximumPoolSize都为1。使用FinalizableDelegatedExecutorService()方法保证线程的唯一性

使用:

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 20; i++) {
            service.execute(mRunnable);
        }
    }

运行结果:

name:pool-1-thread-1    count = 1
name:pool-1-thread-1    count = 2
name:pool-1-thread-1    count = 3
name:pool-1-thread-1    count = 4
name:pool-1-thread-1    count = 5
name:pool-1-thread-1    count = 6
name:pool-1-thread-1    count = 7
name:pool-1-thread-1    count = 8
name:pool-1-thread-1    count = 9
name:pool-1-thread-1    count = 10
name:pool-1-thread-1    count = 11
name:pool-1-thread-1    count = 12
name:pool-1-thread-1    count = 13
name:pool-1-thread-1    count = 14
name:pool-1-thread-1    count = 15
name:pool-1-thread-1    count = 16
name:pool-1-thread-1    count = 17
name:pool-1-thread-1    count = 18
name:pool-1-thread-1    count = 19
name:pool-1-thread-1    count = 20

可以看到所有的任务都是一个线程执行的,因为就只允许创建一个线程。

使用场景:

需要频繁创建线程对象,可以使用newSingleThreadExecutor,避免内存浪费。按顺序执行

4、newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务

构造方法

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

可以看出核心线程数由参数指定,最大线程数是 int 的最大值,使用 DelayedWorkQueue延迟队列

使用:

    public static void main(String[] args) {
        ExecutorService service = Executors.newScheduledThreadPool(3);
        for (int i = 0; i < 20; i++) {
            service.execute(mRunnable);
        }
    }

执行结果:

name:pool-1-thread-1    count = 1
name:pool-1-thread-2    count = 2
name:pool-1-thread-2    count = 3
name:pool-1-thread-1    count = 4
name:pool-1-thread-2    count = 5
name:pool-1-thread-2    count = 6
name:pool-1-thread-3    count = 7
name:pool-1-thread-2    count = 8
name:pool-1-thread-1    count = 10
name:pool-1-thread-2    count = 11
name:pool-1-thread-3    count = 9
name:pool-1-thread-2    count = 13
name:pool-1-thread-1    count = 12
name:pool-1-thread-2    count = 15
name:pool-1-thread-3    count = 14
name:pool-1-thread-2    count = 17
name:pool-1-thread-1    count = 16
name:pool-1-thread-2    count = 19
name:pool-1-thread-3    count = 18
name:pool-1-thread-1    count = 20

支持延时和周期使用:

    public static void main(String[] args) {
    
        ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
        service.scheduleAtFixedRate(mRunnable, 2, 1, TimeUnit.SECONDS);
    }

延迟2秒后,每隔1秒执行一次任务。执行结果

name:pool-1-thread-1    count = 1
name:pool-1-thread-1    count = 2
name:pool-1-thread-2    count = 3
name:pool-1-thread-2    count = 4
name:pool-1-thread-2    count = 5
name:pool-1-thread-2    count = 6
name:pool-1-thread-2    count = 7
name:pool-1-thread-1    count = 8
name:pool-1-thread-1    count = 9
...

使用场景:

计时任务

参考:https://www.cnblogs.com/exe19/p/5359885.html

发布了82 篇原创文章 · 获赞 16 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/sjdjdjdjahd/article/details/94394994