Java线程池使用

作者: 深情不及酒伴 | 来源:发表于2019-07-08 15:01 被阅读8次

    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

    相关文章

      网友评论

        本文标题:Java线程池使用

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