序言

上一篇我们介绍了java里面创建线程的三种方法。使用这三种方式创建的线程,他们的生命周期是需要我们自己去管理的。在高并发的情况下,需要考虑,资源共享,死锁等等,我们自己维护的话就会比较的麻烦,幸运的是,Java提供了线程池去帮助我们管理这些线程。Java提供了四种线程池newFixedThreadPool, newSingleThreadExecutor, newCachedThreadPool, newScheduledThreadPool ,Java使用了Executor创建管理这些线程池。

newFixedThreadPool

这是一种创建的时候就确定大小为n的线程池。这种线程池里面最多有n个线程是active状态的。换而言之最多同时有n个task可以被执行。而且这个线程池里的线程有个特点,如果你不显示的关闭线程池,线程池里面的线程是不会被销毁的,这是一个很重要的特征。对于这个特征,我们可以多思考一些。
1. 不会销毁,就意味着没有创建,销毁这些耗时操作,无疑是有助于提高性能的。
2. 毫无疑问,这个是单例的模式,使用一些功能的时候是可以利用这个特性的。
3. 固定了大小意味着对于资源的消耗是可以预估的,有利于处理outOfMemroy这种情况。

下面的例子展示了它的使用,首先我们先定义三个task。这里使用的是前面提到的Callable<>方式,这种方式和future结合是可以得到线程的执行状态和结果的。

//定义了三个task
class CallableFactory{

    private String threadFlag;

    public CallableFactory(String kindOfThread){
        this.threadFlag = kindOfThread;
    }

    public Callable<String> callableA = new Callable<String>() {

        @Override
        public String call() throws Exception {
            // TODO Auto-generated method stub
            System.out.println("in the "+threadFlag+" A sleep before");
            Thread.sleep(10000);
            System.out.println("in the "+threadFlag+" A sleep after");
            System.out.println("A"+Thread.currentThread().getId());
            return threadFlag+"-A-PoolTest";
        }

    };
    public Callable<String> callableB = new Callable<String>() {

        @Override
        public String call() throws Exception {
            // TODO Auto-generated method stub
            System.out.println("in the "+threadFlag+" B sleep before");
            Thread.sleep(3000);
            System.out.println("in the "+threadFlag+" B sleep after");
            System.out.println("B"+Thread.currentThread().getId());
            return threadFlag+"-B-PoolTest";
        }

    };

    public Callable<String> callableC = new Callable<String>() {

        @Override
        public String call() throws Exception {
            // TODO Auto-generated method stub
            System.out.println("in the "+threadFlag+" C sleep before");
            Thread.sleep(3000);
            System.out.println("in the "+threadFlag+" C sleep after");
            System.out.println("C"+Thread.currentThread().getId());
            return threadFlag+"-C-PoolTest";
        }

    };
}


//使用newFixedThreadPool
private static void fixedThreadPoolTest(){
        ExecutorService executor = Executors.newFixedThreadPool(2);
        CallableFactory factory = new CallableFactory("fixedThreadPool");
        Future futureA = executor.submit(factory.callableA);
        Future futureB = executor.submit(factory.callableB);
        Future futureC = executor.submit(factory.callableC);
        executor.shutdown();
    }

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        long start = System.currentTimeMillis();
//      cacheThreadPoolTest();
//      singleThreadPoolTest();
        fixedThreadPoolTest();
//      scheduledThreadPoolTest();
        long end = System.currentTimeMillis();
        System.out.println("Execution time: " + (end - start));
    }

输出结果是:

java创建的线程池满了但是线程都没有执行完_System

这里我们创建了三个task A, B, C,同时我们固定线程池的大小是2. 然后我们把A, B, C三个task提交到了线程池里面。按照我们前面的理论,最多同时有两个task被执行。这里可能大家没有注意到的是,A, B task被执行的时候是一直占据着两个thread的,等B结束的时候C task才开始执行。也就是说task不会轮流被执行的。而B task在后面开始,先执行完成。这是因为线程池里面的两个thread轮流共享了CPU时间。但是A, B task还是和 thread绑定了,只有一个task执行完成,才会继续执行另外的task。这个在使用的时候也是需要注意的。如果线程池大小固定,但是提交的task过多,有的task会等待很久才会到到结果,当然如果不关心返回值和线程的执行状态,又是另外的结果,因为这个时候是完全异步的,看看Execution time: 3就知道了,主线程是不会被阻塞的。

newSingleThreadExecutor

这是一个单一的线程池,而且执行是有先后顺序的。是根据提交的先后顺序的。一个时刻只能有一个task被执行,只有一个task被执行完成,或者task失败之后,才会执行另外的task。

//使用single thread pool
private static void singleThreadPoolTest() throws InterruptedException, ExecutionException, TimeoutException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        CallableFactory factory = new CallableFactory("singleThreadExecutor");
        Future<String> futureA = executor.submit(factory.callableA);
        Future<String> futureB = executor.submit(factory.callableB);
        Future<String> futureC = executor.submit(factory.callableC);
        executor.shutdown();

    }
//main 测试函数
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        long start = System.currentTimeMillis();
//      cacheThreadPoolTest();
        singleThreadPoolTest();
//      fixedThreadPoolTest();
//      scheduledThreadPoolTest();
        long end = System.currentTimeMillis();
        System.out.println("Execution time: " + (end - start));
    }

执行的结果是:

java创建的线程池满了但是线程都没有执行完_Java_02

可以看到里面的task是根据提交的先后顺序执行的。并且同时只有一个task会被执行。

newCachedThreadPool

前面展示了两种thread pool,你也许会想难道thread pool都是确定大小的。当然不是,下面我们需要讲的就是无限大小的cahce thread pool.当task来到的时候没有可以使用的线程,就会重新创建,直到达到系统允许的最大值。cahce thread pool有个很吸引人的特质,这个线程池里面的thread是会被主动销毁掉的,当一个线程闲置了1分钟之后的,就会被主动的销毁掉

//使用cache thread pool
private static void cacheThreadPoolTest() {
        ExecutorService executor = Executors.newCachedThreadPool();
        CallableFactory factory = new CallableFactory("cacheThreadPool");
        Future<String> futureA = executor.submit(factory.callableA);
        Future<String> futureB = executor.submit(factory.callableB);
        Future<String> futureC = executor.submit(factory.callableC);
        executor.shutdown();
    }
//main 测试方法
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        long start = System.currentTimeMillis();
        cacheThreadPoolTest();
//      singleThreadPoolTest();
//      fixedThreadPoolTest();
//      scheduledThreadPoolTest();
        long end = System.currentTimeMillis();
        System.out.println("Execution time: " + (end - start));
    }

执行结果是:

java创建的线程池满了但是线程都没有执行完_System_03

从结果中可以看到,A, B, C三个task是同时在被执行的,而且是使用了三个线程去执行的,我们使用了Thread.currentThread().getId()得到当前线程的Id,可以看到,确实三个线程在执行这些task。对于1分钟被闲置的线程就会被销毁掉这个特性,大家可以用一个循环去验证,这里就不去实现这一部分了。

newScheduledThreadPool

这个线程池就像名字一样,是可以设置线程执行的时间和周期的。

//使用scheduled thread pool
private static void scheduledThreadPoolTest(){
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(6);
        CallableFactory factory = new CallableFactory("scheduleThreadPool");
        /*Future futureA = executor.submit(factory.callableA);
        Future futureB = executor.submit(factory.callableB);
        Future futureC = executor.submit(factory.callableC);*/
        executor.submit(factory.callableA);
        executor.schedule(factory.callableB, 6000, TimeUnit.MILLISECONDS);
        executor.schedule(factory.callableC, 2000, TimeUnit.MILLISECONDS);
        executor.shutdown();
    }
//main 测试方法
    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        long start = System.currentTimeMillis();
//      cacheThreadPoolTest();
//      singleThreadPoolTest();
//      fixedThreadPoolTest();
        scheduledThreadPoolTest();
        long end = System.currentTimeMillis();
        System.out.println("Execution time: " + (end - start));
    }

下面就是执行的结果:

java创建的线程池满了但是线程都没有执行完_java_04

我们的方法里面是立即执行task A, 延迟6s执行task B, 延迟2s执行task C, 从结果中也可以看到,确实是按照A,C, B的顺序开始执行的,当需要执行schedule task的时候,这会是一个非常有用的方法了。

总结

这篇文章中我们介绍了Java世界中的四中线程池。
1. newFixedThreadPool: 初始固定大小,里面的线程只有在线程池被关闭的时候会被销毁掉。
2. newSingleThreadExecutor: 是一个size是一的线程池,提交到里面的task会按照提交的顺序执行。
3. newCachedThreadPool: 无限大小的线程池,但是里面的线程如果1分钟处于闲置状态,会被主动的销毁掉。
4. newScheduledThreadPool: 这是一个可以设置task执行的时间和周期的一种线程池。
了解四种线程池之后,大家就可以根据自己的需求,选择合适的线程池了。

这篇文章我们主要介绍的是四大线程池,而在上一篇中我们也是着重介绍了实现线程的几种方法,我们并没有对如何拿到线程的执行结果,以及线程的执行状态,做出详细的介绍。下面的文章里面,我会介绍这部分的内容,敬请期待。