1.概述
初学Java多线程,常使用Thread
与Runnable
创建、启动线程。如下例:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
t1.start();
我们需要自己创建、启动Thread对象。
2. 使用Executors执行线程
一些已有的执行器可以帮我们管理Thread对象。你无需自己创建与控制Thread对象。
比如,你不用在代码中编写new Thread
或者thread1.start()
也一样可以使用多线程。如下例:
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {//5个任务
exec.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" doing task");
}
});
}
exec.shutdown(); //关闭线程池
输出如下:
pool-1-thread-2 doing task
pool-1-thread-1 doing task
pool-1-thread-3 doing task
pool-1-thread-4 doing task
pool-1-thread-5 doing task
从输出我们可以看到,exec使用了线程池1中的5个线程做了这几个任务。
这个例子中exec这个Executor负责管理任务,所谓的任务在这里就是实现了Runnable接口的匿名内部类。
至于要使用几个线程,什么时候启动这些线程,是用线程池还是用单个线程来完成这些任务,我们无需操心。完全由exec这个执行器来负责。
在这里exec(newCachedThreadPool)指向是一个可以根据需求创建新线程的线程池。
2.1 Executors工具类
Executors相当于执行器的工厂类,包含各种常用执行器的工厂方法,可以直接创建常用的执行器。几种常用的执行器(静态方法)如下:
Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池
Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池
Executors.newScheduledThreadPool(corePoolSize); // 创建一个定长线程池,支持定时及周期性任务执行。
在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是经常使用Executors类中提供的几个静态方法来创建线程池。
从下面它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。
2.1.1 newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程(线程池中曾经创建的线程,在完成某个任务后也许会被用来完成另外一项任务),若无可回收,则新建线程。
实现原理:将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,
使用的是没有容量的SynchronousQueue(无界),但其maximumPoolSize是无界的,也就是说来了任务就创建线程运行,并且当主线程提交任务的速度高于maximumPoolSize中线程处理任务的速度时CachedThreadPool将会不断的创建新的线程。
适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。在极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
60L:当线程空闲超过60秒,就销毁线程。
2.1.2 newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
这个创建的线程池corePoolSize和maximum PoolSize值是相等的,都为nThreads。
keepAliveTime=0L:线程一空闲,就立即销毁。
它使用的LinkedBlockingQueue(无界队列)。使用该队列作为工作队列会对线程池产生如下影响:
(1)当前线程池中的线程数量达到corePoolSize后,新的任务将在无界队列中等待;
(2)由于我们使用的是无界队列,所以参数maximumPoolSize和keepAliveTime无效;
(3)由于使用无界队列,运行中的FixedThreadPool不会拒绝任务(当然此时是未执行shutdown和shutdownNow方法)。
所以不会去调用RejectExecutionHandler的rejectExecution方法抛出异常。
创建一个定长线程池(最大线程数是nThreads),可控制线程最大并发数,超出的线程会在队列LinkedBlockingQueue中等待。适用于为了满足资源管理要求,而需要限制当前线程数量的应用场景。
线程在执行完自己当前任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
2.1.3 newSingleThreadExecutor()
public static ExecutorService newSingleThreadPool() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。这种特点可以被用来处理共享资源的问题而不需要考虑同步的问题。
适用于需要保证顺序地执行各个任务,并且在任意时间点,不会有多个线程是活动的应用场景。
它使用的LinkedBlockingQueue(无界队列)。
2.1.4 newSingleThreadScheduledExector()
只包含一个线程的定时及周期线程池。即使任务再多,也只用1个线程完成任务。
2.1.5 newScheduledThreadPool ()
创建一个定长线程池,支持定时及周期性任务执行。
适用于多个后台线程执行周期任务,同时为了满足资源管理的需求而限制后台线程的数量的应用场景。
2.1.6 表格对比
使用的阻塞队列 | corePoolSize | maximumPoolSize | 适用场景 | |
---|---|---|---|---|
newCachedThreadPool() | SynchronousQueue 无界 | 0 | Integer.MAX_VALUE | 很多的短期异步任务的小程序,或者是负载较轻的服务器。 |
newFixedThreadPool() | LinkedBlockingQueue 无界 | =nThreads | =nThreads | 为了满足资源管理要求,而需要限制当前线程数量 |
newSingleThreadExecutor() | LinkedBlockingQueue 无界 | 1 | 1 | 于需要保证顺序地执行各个任务,并且在任意时间点,不会有多个线程是活动 |
newSingleThreadScheduledExector() | 一个线程 | 定时及周期线程池 | ||
newScheduledThreadPool () | 定长线程池 | 多个后台线程执行周期任务,限制后台线程的数量 |