Java 并发编程(六)线程池Executors与ThreadPoolExecutor

为什么使用线程池?

在开发服务器软件时,我们经常需要处理执行时间比较短但是数量巨大的请求,如果每个请求都创建一个新的线程来处理,那就会导致线程太多而遇上系统性能的瓶颈,因为线程的创建和销毁需要JVM进行处理,如果请求时间太短,那就在线程的创建和销毁对象上花费的时间大于线程执行的时间,如果是这样,那性能就大大降低了。
考虑到以上问题,java在jdk5中提供了线程池的支持,当然我们可以自己来实现线程池。jdk5中的线程池主要是用来提供高并发的访问处理,并且还可以复用池中空闲的线程对象。核心机制就是提供一个执行效率比较高的线程池,对线程池中的线程对象管理,包含创建和销毁,只需要将任务传递给线程池即可,线程对象的处理都在线程池中。

Executor接口

我们要创建线程池钱,先来了解一下Executor接口,因为大部分创建线程池的类都实现了此接口。可查看此接口的源码,只有一个方法如下:

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the <tt>Executor</tt> implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution.
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

Executor是接口,不能直接使用,还得需要它的实现类,下图就是比较完整的Executor接口相关的继承结构

Executor继承结构

ExecutorService是Executor子接口,所以也不能实例化,但其内部提供了比较多的方法,如下图:

ExecutorService提供的方法

ExecutorService唯一一个实现类AbstractExecutorService,从名字上看是抽象的,所以也不能实例化,我们再看一下AbstractExecutorService的子类ThreadPoolExecutor,这个类不是抽象的,可以实例化,所以使用这个类中的方法来实现创建多线程。其类结构如下:

ThreadPoolExecutor方法

以上就是对线程池的创建应用比较多的接口Executor和子接口ExecutorService的说明,再到可实例化的对象ThreadPoolExecutor的简单描述,下面我们看下具体创建线程池的类以及使用方法。

线程池的创建

JDK5中提供了两种方式来创建线程池,Executors工厂类和ThreadPoolExecutor对象,下面我们看看这两个的创建细节

Executors工厂类创建线程池

类中方法如下:

Executors类中方法

Executors.newCachedThreadPool()方法创建线程池

此方法创建的是无界的线程池,自己管理线程池。此线程池中存放的个数最大值是Integer.MAX_VALUE。

示例:

public class ExecutorsDemo {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis());
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis());
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis());
            }
        });
    }
}

执行结果:

pool-1-thread-1 begin 1506671576457
pool-1-thread-2 begin 1506671576458
pool-1-thread-2 end 1506671578460
pool-1-thread-1 end 1506671578460

从结果中可以看到,创建了两个线程,而且执行是无序的。

Executors.newFixedThreadPool(int) 创建有界线程池

此方法指明了可以创建线程的最大数,当然线程也可以复用。

示例1:

public class MyRunnable implements Runnable {
    private String name;

    public MyRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " name = " + name + " begin with " + System.currentTimeMillis());
            TimeUnit.SECONDS.sleep(2);
            System.out.println(Thread.currentThread().getName() + " name = " + name + " end with " + System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

调试程序:

public class Main {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 3; i++) {
            service.execute(new MyRunnable((i + 1) + ""));
        }

        for (int i = 0; i < 3; i++) {
            service.execute(new MyRunnable((i + 1) + ""));
        }

        service.shutdown();
    }
}

调试结果:

pool-1-thread-1 name = 1 begin with 1506691581334
pool-1-thread-2 name = 2 begin with 1506691581334
pool-1-thread-3 name = 3 begin with 1506691581334
pool-1-thread-1 name = 1 end with 1506691583336
pool-1-thread-2 name = 2 end with 1506691583336
pool-1-thread-3 name = 3 end with 1506691583336

pool-1-thread-2 name = 2 begin with 1506691583336
pool-1-thread-1 name = 1 begin with 1506691583336
pool-1-thread-3 name = 3 begin with 1506691583336
pool-1-thread-3 name = 3 end with 1506691585340
pool-1-thread-2 name = 2 end with 1506691585340
pool-1-thread-1 name = 1 end with 1506691585340

从输出结果可以看出,最多创建了3个线程,所以此方法创建的最多线程数是可以控制的。

Executors.newSingleThreadExecutor()方法创建单一线程池

此方法创建的线程池中只有一个线程,单一线程池可以实现以队列形式来执行任务。
示例:
创建MyRunnable类,参考以上示例。
调试代码:

public class Main {
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 3; i++) {
            service.execute(new MyRunnable((i + 1) + ""));
        }
        service.shutdown();
    }
}

调试结果:

pool-1-thread-1 name = 1 begin with 1506692005832
pool-1-thread-1 name = 1 end with 1506692007836
pool-1-thread-1 name = 2 begin with 1506692007837
pool-1-thread-1 name = 2 end with 1506692009838
pool-1-thread-1 name = 3 begin with 1506692009838
pool-1-thread-1 name = 3 end with 1506692011841

从结果可以看出,此线程池中只有一个线程来执行任务。

Executors.newXXX(ThreadFactory factory) 添加此参数

除了以上三个创建线程池的方法,另外还有ThreadFactory的参数,此参数是用来对线程池中线程自定义一些特殊标识,查看ThreadFactory源码中发现,只有一个方法,如下:

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

再次就不多说了,有需要自定义的话可以实现这个接口来自定义线程。

ThreadPoolExecutor类

Executors工具类中提供的几种创建线程池的方法无非是java程序的语法糖,实际底层创建线程池的还是ThreadPoolExecutor类,只是不同方法参数有所差异。
我们可以看下 Executors.newCachedThreadPool()方法的源代码,可以看到也是使用ThreadPoolExecutor来实现的多线程,具体参数是什么意思,后面会说明。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

ThreadPoolExecutor类中最常用的构造方法是:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable>() queue);

参数说明:

  • corePoolSize :线程池中保存的线程数,包括空闲线程,也是核心池的大小
  • maximumPoolSize :线程池中最大线程数
  • keepAliveTime:当线程数 > corePoolSize 时,在没有超过指定时间内不会从线程池中将空闲线程删除,如果超过此时间,则删除,如果设置为0,那多余线程执行完任务后立刻从线程池中删除。
  • unit :keepAliveTime的时间单位
  • queue:执行前用于保存任务的队列,此队列仅仅保持有execute方法提交的Runnable任务。

有了上面的说明,可以还不是很理解,下面我们就从两个方面来说明如何正确创建线程池。根据参数对象BlockingQueue来学习,此参数有两个对象可传递,一个是LinkedBlockingQueue对象,另一个是SynchronousQueue对象,传递的参数不一样,对于线程池中的实现也不一样,下面我们一一说明,先来说LinkedBlockingQueue对象。

参数:LinkedBlockingQueue对象

当执行的线程数threadCount <= corePoolSize 时,看以下代码:

 private static void lessThanCorePoolSizeWithLinkedBlockingQueue(Runnable runnable) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(7, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        for (int i = 0; i < 7; i++) {
            executor.execute(runnable);
        }
        executor.shutdown();
    }

测试结果:

pool-1-thread-1 run : 1506694684337
pool-1-thread-2 run : 1506694684337
pool-1-thread-3 run : 1506694684337
pool-1-thread-4 run : 1506694684338
pool-1-thread-5 run : 1506694684338
pool-1-thread-6 run : 1506694684338
pool-1-thread-7 run : 1506694684338

从结果上可以看出,执行的线程数 threadCount <= corePoolSize时,立刻创建线程去执行任务,不放入扩展队列Queue中,其他参数忽略

当执行的线程数threadCount > corePoolSize 时,看以下代码:

public static void greaterThanCorePoolSizeWithLinkedBlockingQueue(Runnable runnable) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(7, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        for (int i = 0; i < 9; i++) {
            executor.execute(runnable);
        }
        executor.shutdown();
    }

测试结果:

pool-2-thread-1 run : 1506694684339
pool-2-thread-2 run : 1506694684339
pool-2-thread-3 run : 1506694684339
pool-2-thread-4 run : 1506694684339
pool-2-thread-5 run : 1506694684339
pool-2-thread-6 run : 1506694684339
pool-2-thread-7 run : 1506694684339
pool-2-thread-1 run : 1506694685340
pool-2-thread-2 run : 1506694685340

从结果中可以看到,及时执行的任务数超过了corePoolSize,创建的线程数最多是corePoolSize个数,多余的任务会放到队列中等待执行。

结论:

ThreadPoolExecutor若使用LinkedBlockingQueue作为执行任务队列,则会忽略maximumPoolSize参数设置,将 (执行线程数 - corePoolSize) 的线程放入队列中等待执行。

全部代码清单如下:
MyRunnable类,

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " run : " + System.currentTimeMillis());
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试类LinkedBlockingQueueDemo:

package threadpoolexecutordemo.linkedblockingqueuedemo;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class LinkedBlockingQueueDemo {

    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        createThreadPoolWithLinkedBlockingQueue(runnable);
    }

    /**
     * ThreadPoolExecutor 使用 LinkedBlockingQueue 作为线程队列
     */
    public static void createThreadPoolWithLinkedBlockingQueue(Runnable runnable) {

        /**
         * 执行的线程数 threadCount <= corePoolSize
         * 结果:立刻创建线程去执行任务,不放入扩展队列Queue中,其他参数忽略
         */
        lessThanCorePoolSizeWithLinkedBlockingQueue(runnable);

        System.out.println("=====================");

        /**
         * 执行的线程数 threadCount > corePoolSize
         * 结果:会立刻执行线程任务,生成的线程数为corePoolSize值,并且maximumPoolSize忽略,
         * 将 (执行线程数 - corePoolSize) 的线程放入LinkedBlockingQueue队列中等待执行
         */
        greaterThanCorePoolSizeWithLinkedBlockingQueue(runnable);

    }

    private static void lessThanCorePoolSizeWithLinkedBlockingQueue(Runnable runnable) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(7, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        for (int i = 0; i < 7; i++) {
            executor.execute(runnable);
        }
        executor.shutdown();
    }

    public static void greaterThanCorePoolSizeWithLinkedBlockingQueue(Runnable runnable) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(7, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        for (int i = 0; i < 9; i++) {
            executor.execute(runnable);
        }
        executor.shutdown();
    }
}
参数:SynchronousQueue对象

当执行的线程数threadCount <= corePoolSize 时,看以下代码:

private static void lessThanCorePoolSizeWithSynchronousQueue(Runnable runnable) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(7, 10, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        for (int i = 0; i < 7; i++) {
            executor.execute(runnable);
        }
        executor.shutdown();
    }

执行结果:

pool-1-thread-1 run : 1506695360809
pool-1-thread-4 run : 1506695360809
pool-1-thread-5 run : 1506695360809
pool-1-thread-3 run : 1506695360809
pool-1-thread-2 run : 1506695360809
pool-1-thread-6 run : 1506695360810
pool-1-thread-7 run : 1506695360810

从结果看出,当执行的线程数 threadCount <= corePoolSize,立刻创建线程去执行任务,不放入扩展队列Queue中,其他参数忽略。

当执行的线程数 threadCount > corePoolSize && threadCount <= maximumPoolSize时,代码如下:

  public static void greaterThanCorePoolSizeWithSynchronousQueue(Runnable runnable) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(7, 10, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        for (int i = 0; i < 9; i++) {
            executor.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(executor.getCorePoolSize());
        System.out.println(executor.getPoolSize());
        System.out.println(executor.getQueue().size());
        executor.shutdown();
    }

执行结果:

pool-1-thread-2 run : 1506695459525
pool-1-thread-1 run : 1506695459525
pool-1-thread-3 run : 1506695459525
pool-1-thread-4 run : 1506695459525
pool-1-thread-5 run : 1506695459526
pool-1-thread-6 run : 1506695459526
pool-1-thread-7 run : 1506695459526
pool-1-thread-8 run : 1506695459526
pool-1-thread-9 run : 1506695459526
7
9
0

从结果可看出,当执行的线程数 threadCount > corePoolSize && threadCount <= maximumPoolSize时,会立刻执行线程任务,并不将 (执行线程数 - corePoolSize) 的线程放入SynchronousQueue列中,在执行完任务后指定时间后发生超时时将空闲线程进行清除。

当执行的线程数 threadCount > maximumPoolSize时,代码如下:

  public static void greaterThanMaxPoolSizeWithSynchronousQueue(Runnable runnable) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(7, 10, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        for (int i = 0; i < 11; i++) {
            executor.execute(runnable);
        }
        try {
            Thread.sleep(1000);
            System.out.println(executor.getCorePoolSize());
            System.out.println(executor.getPoolSize());
            System.out.println(executor.getQueue().size());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdownNow();
        }
    }

执行结果:

pool-1-thread-1 run : 1506695562414
pool-1-thread-2 run : 1506695562414
pool-1-thread-3 run : 1506695562414
pool-1-thread-4 run : 1506695562414
pool-1-thread-5 run : 1506695562414
pool-1-thread-6 run : 1506695562415
pool-1-thread-7 run : 1506695562415
pool-1-thread-8 run : 1506695562415
pool-1-thread-9 run : 1506695562415
pool-1-thread-10 run : 1506695562415
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task threadpoolexecutordemo.linkedblockingqueuedemo.MyRunnable@5ef94934 rejected from java.util.concurrent.ThreadPoolExecutor@6139cf9c[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
    at threadpoolexecutordemo.linkedblockingqueuedemo.SynchronousQueueDemo.greaterThanMaxPoolSizeWithSynchronousQueue(SynchronousQueueDemo.java:76)
    at threadpoolexecutordemo.linkedblockingqueuedemo.SynchronousQueueDemo.createThreadPoolWithSynchronousQueue(SynchronousQueueDemo.java:46)
    at threadpoolexecutordemo.linkedblockingqueuedemo.SynchronousQueueDemo.main(SynchronousQueueDemo.java:16)

从结果中看出,当执行的线程数 threadCount > maximumPoolSize时,会立刻执行线程任务,但是只会处理maximumPoolSize个任务,其他任务不处理并且抛出异常,此时shutdown()方法将失效。
结论:

当ThreadPoolExecutor使用SynchronousQueue作为任务队列时,执行的线程数 最大值为maximumPoolSize,超过最大值,只会处理maximumPoolSize个任务,其他任务不处理并且抛出异常,小于最大值,则程序执行没有问题,但是并不将 (执行线程数 - corePoolSize) 的线程放入SynchronousQueue列中。

以上就是我对Executors工厂类以及ThreadPoolExecutor类的总结,希望对看官有些帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值