如果用new Thread().start的方式使用线程,当线程执行完毕后会销毁,如果频繁地创建和销毁线程,对系统的消耗将会非常大。那么如果线程执行完任务后还可以重新使用,那么就可以提高性能。线程JDK里面线程池主要使用的是ThreadPoolExecutor,现在来初步感受一下线程池的使用:
package com.rancho945.concurrent;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 100, TimeUnit.SECONDS, new LinkedBlockingQueue());
for (int i = 0; i < 8; i++) {
//只要实现了Runnable接口的类都可以往里面扔
executor.execute(new WorkTask(i));
}
}
static class WorkTask implements Runnable{
final private int taskNum;
public WorkTask(int num) {
taskNum = num;
}
@Override
public void run() {
System.out.println("hello world,I am task "+taskNum+"----executed by "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
执行结果:
hello world,I am task 1----executed by pool-1-thread-2
hello world,I am task 2----executed by pool-1-thread-3
hello world,I am task 0----executed by pool-1-thread-1
hello world,I am task 3----executed by pool-1-thread-4
hello world,I am task 4----executed by pool-1-thread-5
hello world,I am task 5----executed by pool-1-thread-2
hello world,I am task 6----executed by pool-1-thread-4
hello world,I am task 7----executed by pool-1-thread-1
使用线程池的主要步骤是,先创建线程池,然后把实现了Runnable接口的对象传进execute方法中即可。下面我们来仔细说说有关线程池的那些事。
ThreadPoolExecutor的构造函数有四个:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue 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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
这些构造函数最终都会调用到最后一个构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
我们一个一个来解释一些这些构造函数的含义:
核心池的大小。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。如果在执行任务之前调用了prestartAllCoreThreads()会创建好所有的核心线程,调用prestartCoreThread()方法会创建好一个核心线程。
最大线程数量,包含corePoolSize。当任务超过核心线程池大小的时候,先把任务放在同步队列中,如果同步队列满了,再创建线程,直到线程池大小到达maximumPoolSize为止。
线程存活时间,默认只针对非核心线程。如果一个非核心线程在空闲状态下,到达了keepAliveTime,那么该线程会销毁。如果调用了allowCoreThreadTimeOut(true),那么对核心线程也会起作用,直到线程数为零。
keepAliveTime的时间单位,有以下几种:
1. TimeUnit.DAYS; //天
2. TimeUnit.HOURS; //小时
3. TimeUnit.MINUTES; //分钟
4. TimeUnit.SECONDS; //秒
5. TimeUnit.MILLISECONDS; //毫秒
6. TimeUnit.MICROSECONDS; //微妙
7. TimeUnit.NANOSECONDS; //纳秒
实现了BlockingQueue的阻塞队列,用来存储等待执行的任务,超出核心线程池的任务会放在这个队列当中。阻塞队列主要有以下几种选择:
//基于数组的阻塞队列,创建的时候必须指定大小
1. ArrayBlockingQueue;
//基于链表的阻塞队列,可以不指定大小,默认为Integer.MAX_VALUE
2. LinkedBlockingQueue;
//同步队列,它是没有容量的,不会保存提交的任务。
//也就是说如果使用这个队列,那么当任务数超出核心线程数后,直接新建非核心线程执行新任务。
3. SynchronousQueue;
//有优先级的阻塞队列,如果任务有优先级,那么会根据优先级来取出任务,比如后面加进来的任务优先级比较高,那么会执行后面的任务,而不是哪个先进队就执行哪个。
4. PriorityBlockingQueue
线程工厂,主要是创建线程。默认为Executors中的DefaultThreadFactory,这里暂时不用管,只知道能创建线程即可。也可以自己定义,只需要实现ThreadFactory接口即可。
当任务超出最大线程值+队列最大值的时候,线程池就会把提交的任务拒绝。handler作为拒绝任务的处理策略,有下面几种
//丢弃任务并抛出RejectedExecutionException异常,线程池默认是该策略
1. ThreadPoolExecutor.AbortPolicy。
//什么都不做,默默地丢弃任务,也不抛出异常。
2. ThreadPoolExecutor.DiscardPolicy
//丢弃队列最前面的任务,然后重新尝试执行任务(如果还是失败则重复此过程)
3. ThreadPoolExecutor.DiscardOldestPolicy
//由调用线程处理该任务,比如说在主线程中调用线程池的execute方法被拒绝,该任务在主线程中执行,相当于在主线程中直接执行run方法。
4. ThreadPoolExecutor.CallerRunsPolicy
Executors(注意多了个s)类提供了几个静态方法创建线程池,一般情况下用静态方法创建线程更加方便一些:
//创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newCachedThreadPool();
//创建容量为1的缓冲池(单线程线程池)
Executors.newSingleThreadExecutor();
//创建固定容量大小的缓冲池
Executors.newFixedThreadPool(int);
如果理解了前面的,那么看看这几个静态方法便一目了然。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
线程池的关闭有两个方法:
//调用该方法后不会接受新的任务,等待队列中的任务执行完成后关闭线程池
public void shutdown();
//该方法尝试中断正在执行的任务,中断的方式是调用线程的interrupt方法,并且只调用一次,如果线程再次阻塞,则不能保证任务能够完全执行完。队列中没有执行完的任务以list的形式返回。
public List shutdownNow();
另外线程池还有submit、invokeAll、invokeAny用于执行有返回结果以及批量执行有返回结果的任务。这里涉及到Future等接口,这里暂时不说。
- JDK源码
- 《Java并发编程的艺术》 方腾飞 著
- 《Java并发编程实战》 Brian Goetz等著 童云兰等译