为了避免系统频繁地创建和销毁线程,我们可以让创建的线程复用。在使用线程池后,创建线程变成了从线程池中获得空闲线程,关闭线程变成了向线程池中归还线程(类似数据库连接池)。
Executor框架提供了各种类型的线程池,主要有以下工厂方法:
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。多于可用线程的任务会被保存在任务队列中,等待线程空闲后处理,
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
一个固定大小的线程池的例子:
public class ThreadPoolDemo {
public static class MyTask implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + ":Thread ID:" + Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyTask myTask = new MyTask();
ExecutorService es = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
es.submit(myTask);
}
}
}
另一个需要注意的是方法是newScheduledThreadPool() 。它可以根据时间需要对线程进行调度。主要方法有:
schedule(Runnable command,long delay, TimeUnit unit)
创建并执行在给定延迟后启用的一次性操作
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnitunit)
创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
//如果前面的调度没有完成,那么调度不会启动,也就是说,如果任务执行时间大于间隔时间,在上一个任务执行完成后就会紧接着执行下一个任务
//而如果使用ses.scheduleWithFixedDelay()方法,那么在上一个任务完成后会在固定的间隔时间之后开始下一个任务
ses.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(System.currentTimeMillis()/1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},0,2,TimeUnit.SECONDS);
}
}
对于核心的几个线程池,如newFiexedThreadPool、newSingleThreadExecutor和newCachedThreadPool,都只是对ThreadPoolExecutor类的封装。ThreadPoolExecutor类的构造方法如下:
public ThreadPoolExecutor(int corePoolSize,//指定了线程池中的线程数量
int maximumPoolSize,//最大线程数量
long keepAliveTime,//当线程池中的线程数量超过corePoolSize时,多余线程的存活时间
TimeUnit unit,//时间单位
BlockingQueue workQueue,//任务队列
ThreadFactory threadFactory,//线程工厂,用于创建线程
RejectedExecutionHandler handler//拒绝策略。当任务太多来不及处理时,如何拒绝任务。)
ThreadPoolExecutor的最后一个参数制定了拒绝策略,这是系统超负荷运行时的补救措施。JDK内置了四种拒绝策略:
1、AbortPolicy策略
该策略直接抛出异常,阻止系统工作
2、CallerRunsPolicy策略
只要线程池未关闭,该策略直接在调用者线程中运行当前被丢弃的任务。显然这样不会真的丢弃任务,但是,调用者线程性能可能急剧下降。
3、DiscardOledestPolicy策略
丢弃最老的一个请求任务,也就是丢弃一个即将被执行的任务,并尝试再次提交当前任务。
4、DiscardPolicy策略
默默的丢弃无法处理的任务,不予任何处理。
内置策略均实现了RejectedExecutionHandler接口,若以上策略无法满足需求,可以自己扩展接口实现自定义策略。
public class RejectThreadPoolDemo {
public static class MyTask implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + ":Thread ID:" +
Thread.currentThread().getId());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
//自定义线程池
ExecutorService es = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque(10),
Executors.defaultThreadFactory(),
new RejectedExecutionHandler() {
@Override//实现接口方法
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+" is discard");
}
});
for (int i = 0; i < Integer.MAX_VALUE; i++) {
es.submit(task);
Thread.sleep(10);
}
}
}
ThreadFactory是一个接口,他只有一个方法:Thread newThread(Runable r)
当线程需要新建线程时,就会调用这个方法。
我们可以自定义ThreadFactory,实现自己的线程创建方法。
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
ExecutorService es = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
new SynchronousQueue(),
new ThreadFactory() {
@Override//自定义线程工厂
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
System.out.println("create " + t);
return t;
}
});
for (int i = 0; i < 5; i++) {
es.submit(task);
}
Thread.sleep(2000);
}
}
JDK提供的并发容器大部分在java.util.concurrent下:
ConcurrentHashMap:一个高效并发的HashMap。是线程安全的。
CopyOnWriteArrayList:一个List,在读多于写的场合性能远远优于Vector。
ConcurrentLinkedQueue:高效的并发队列,使用链表实现。
ConcurrentSkipListMap:跳表的并发实现。