在阅读了《Android开发进阶,从小工到专家》的3.2.4.0~3.2.2.0 内容后启发很大,所以写了一篇博客总结一下关于线程池的部分内容。 


0.首先什么是线程池?

线程池就是创建多个线程并且进行管理的容器(线程池是个容器,可以创建线程和管理线程,并且给线程分配任务)


1.为什么要用线程池呢?

我们都知道,在Java中创建一个线程其实是一个很简单的事情,只要new Thread就可以了,但是这样做并不是一种很好的方式。那么为什么不好呢?

比如在一个项目里,全部都是用的new Thread的方式去启用线程,那么创建好Thread1,而1在运行的时候,创建了Thread2,等等等... 创建了10个线程的时候,

1,2,3都执行完毕了但是没被销毁,就可能导致无限制的新建线程,相互竞争,占用过多的系统资源,导致死锁以及OOM。

而且这些线程缺乏统一的管理的功能,也缺乏定期执行,定时执行,线程中断的功能。

说了这么多,那么线程池的好处是啥啊?

线程池的好处有以下3方面:

(1) 重用已经存在的线程,减少了线程的创建和销毁的开销。

(2)可有效控制最大并发的线程数,提高了系统资源的使用率避免很多竞争,避免了OOM啊 死锁啊等。

(3)可以提供定时和定期的执行方式,单线程,并发数量的控制等功能!

线程池可以使得对线程的管理更加方便,并且对高并发的控制尽在掌握。


2.四种Java线程池功能及分析

线程池都继承了ExecutorService的接口,所以他们都具有ExecutorService的生命周期方法:运行,关闭,终止;

因为继承了ExecutorService接口,所以它在被创建的时候就是处于运行状态,当线程没有任务执行时,就会进入关闭状态,只有调用了shutdown()的时候才是正式的终止了这个线程池。

java通过Executors工厂类提供我们的线程池一共有4种:

fixedThreadPool() //启动固定线程数的线程池

CachedThreadPool() //按需分配的线程池

ScheduledThreadPoolExecutor()//定时,定期执行任务的线程池

ThreadPoolExecutor()//指定线程数的线程池。


我们先来说参数最多的线程,方便下面对参数的理解,

ThreadPoolExecutor()

ThreadPoolExecutor是线程池的实现类之一,它主要是启动指定数量线程以及将任务添加到队列,并且将任务发送给空闲的线程。

我们来看一下拥有最多构造函数的线程池的构造函数吧:

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

corePoolSize : 线程的核心线程数。

maximumPoolSize:线程允许的最大线程数。

keepAliveTime:当前线程池 线程总数大于核心线程数时,终止多余的空闲线程的时间。

Unit:keepAliveTime参数的时间单位

workQueue:队伍队列,如果线程池达到了核心线程数,并且其他线程都处于活动状态的时候,则将新任务放入此队列。

threadFactory:定制线程的创建过程

Handler:拒绝策略,当workQueue队满时,采取的措施。

ThreadPoolExecutor()的作用主要是让我们更灵活的使用线程池,这里不详细的举例了。



fixedThreadPool() //启动固定线程数的线程池

在Android中,由于系统资源有限,所以我们最常用的就是fixedThreadPool()。

fixedThreadPool(int size) 就只有一个参数,size,就是线程池中最大可创建多少个线程

我们来看一下该线程池的实现过程


在实现上跟ThreadPoolExecutor类似fixedThreadPool简化了实现过程,把corePoolSize和maximumpoolSize的值都设为传入的size,并且设置keepAliveTime为0ms,然后采用的是LinkedBlockingQueue队列,这个队列是链式结构,所以是无边界的。可以容纳无数个任务。

总结:比如我创建3个线程的fixedThreadPool ,当3个都为活跃的时候,后面的任务会被加入无边界的链式队列(LinkedBlockingQueue),有空闲,就执行任务。

使用过程:

private static void fixedThreadPool(int size) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(size);
//        ExecutorService executorService2 = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            Future<Integer> task = executorService.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    System.out.println("执行线程" + Thread.currentThread().getName());
                    return fibc(40);
                }
            });
            System.out.println("第"+i+"次计算,结果:"+task.get());
        }

结果:



CachedThreadPool() //按需分配的线程池

对比fixedThreadPool来说 CachedThreadPool就要更快一些,为什么快呢?

我们来看一下CachedThreadPool的实现源码:


可以看到,这里的  maximumPoolSize:线程允许的最大线程数。  为MAX_VALUE(无限大),

也就是说,只要有任务并且其他线程都在活跃态,就会开启一个新的线程 (因为没有上限)

而当有空闲的线程的时候,就会去调用空闲线程执行任务。


使用过程:

 private static void newCacheThreadPool(){
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            Future task = executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"---->"+fibc(20));
                }
            });
        }
    }

结果:




ScheduledThreadPoolExecutor() 定时定期的线程池


创建ScheduledThreadPoolExecutor很简单,只要传入corePoolSize()线程的核心线程数。就可以开启这个线程池

然后我们需要使用它的定时任务,就需要实现他的scheduleAtFixedRate方法 这个方法有4个参数:


第一个为要执行的任务。

第二个为每次任务执行的延迟,比如传入1,就会每隔1秒执行一次。

第三个为执行的周期

第四个为第二个参数的时间单位。


*无论实现几个scheduleAtFixedRate方法,他们都互不干扰。


实现如下

private static void newScheduledThreadPool(){
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"----->" + fibc(40));
            }
        },5,3,TimeUnit.SECONDS);

        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"----->" + fibc(5));
            }
        },1,1,TimeUnit.SECONDS);


        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"----->" + fibc(1));
            }
        },1,2,TimeUnit.SECONDS);
    }

我这里实现了3个scheduleAtFixedRate方法,

第一个为5s一次,3个周期执行一次

第二个为1s一次,1个周期执行一次

第三个为1s一次,2个周期执行一次


结果如下:




总结:线程池的使用,可以更规范的管理和创建销毁线程,也可以更多样化的去使用线程,减小我们的系统开支。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐