避免系统频繁创建和销毁线程,对创建地线程进行复用。即使用线程时直接从线程池获取空闲线程,关闭线程时将线程退回到线程池。
通过Executors进行创建,有以下几种方式:
下面用newFixedThreadPool进行测试。
package com.demo1.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo1 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 20; i++) {
executorService.execute(new PrintCurrentTimeTask());
}
}
private static class PrintCurrentTimeTask implements Runnable {
@Override
public void run() {
try {
System.err.println(Thread.currentThread() + "执行任务");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上述代码创建了5个线程的线程池,也就是同时只能有5个线程执行。剩余的任务将等待。
newSingleThreadSchedulerExecutor以及newSchedulerThreadPool
最终将创建ScheduledThreadPoolExecutor对象,该类直接继承ThreadPoolExecutor。
下面介绍三个重要的方法:
方法 | 说明 |
---|---|
schedule | 延时执行 |
scheduleAtFixedRate | 创建一个周期性任务,第n次执行时间:initialDelay + n * period |
scheduleWithFixedDelay | 创建一个周期性任务,每一次执行再前一次执行完成后,过period时间执行 |
package com.demo1.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo1 {
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
//延迟5s后执行
executorService.schedule(new Delay5SecondTask(), 3, TimeUnit.SECONDS);
//延迟4s执行,周期为2s
executorService.scheduleWithFixedDelay(new CycleTask(), 4, 2, TimeUnit.SECONDS);
}
/**
* 延迟5s后执行
*/
private static class Delay5SecondTask implements Runnable {
@Override
public void run() {
System.err.println("------------延迟3s后执行-------------");
}
}
/**
* 周期执行
*/
private static class CycleTask implements Runnable {
@Override
public void run() {
System.out.println("start:" + System.currentTimeMillis());
System.err.println("oooooooooooooo执行任务oooooooooooooo");
System.out.println("end:" + System.currentTimeMillis());
}
}
}
使用scheduleAtFixedRate存在的问题,如果线程执行时间大于周期会发生什么情况?
——答案是会立即直接
下面代码,延迟4s执行,周期为2s,而每个任务需要执行3s
package com.demo1.thread;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo1 {
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
//延迟4s执行,周期为2s
executorService.scheduleAtFixedRate(new CycleTask(), 4, 2, TimeUnit.SECONDS);
}
/**
* 执行时间为3s的任务
*/
private static class CycleTask implements Runnable {
@Override
public void run() {
try {
System.out.println("start:" + System.currentTimeMillis());
Thread.sleep(3000);
System.out.println("end:" + System.currentTimeMillis());
} catch (InterruptedException e) {}
}
}
}
——无论通过哪种方式创建,实际上都是创建一个ThreadPoolExecutor对象(就是线程池)。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
/* 省略 */
}
参数 | 说明 |
---|---|
corePoolSize | 线程池中线程数量 |
maximumPoolSize | 线程池中最大线程数量 |
keepAliveTime | 线程数量超过corePoolSize,多余空闲线程的存活时间 |
unit | keepAliveTime的时间单位 |
workQueue | 任务队列,存放还未被执行的任务 |
threadFactory | 用于创建线程的线程工厂 |
handler | 拒绝策略 |
其实还有一个PriorityBlockingQueue:一个具有优先级的无限阻塞队列。(只提一下)
(一)有界任务队列
(二)无界任务队列
使用newFixedThreadPool()创建一个线程池,使用的是无界的任务队列。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
使用newSingleThreadExecutor()创建一个线程池,使用的也是无界的任务队列,与newFixedThreadPool的唯一区别就是使用单线程。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
(三)直接提交队列
使用newCachedThreadPool()创建线程池,使用的是直接提交的队列。可以看到多余的线程将在60s后回收掉。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
JDK内置的四种拒绝策略
拒绝策略 | 说明 |
---|---|
AbortPolicy | 直接抛出异常 |
CallerRunsPolicy | 只要线程池未关闭,直接在调用者线程中执行被丢弃的任务 |
DiscardOldestPolicy | 丢弃一个最老的请求,并尝试再次提交该任务 |
DiscardPolicy | 直接丢弃 |
我们可以通过自定义线程工程来创建我们想要的线程
package com.demo1.thread;
import java.util.concurrent.*;
public class Demo1 {
public static void main(String[] args) {
new ThreadPoolExecutor(5, 5, 0, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setPriority(5);
return new Thread(r);
}
});
}
}
重写线程池的一些方法,以达到一些扩展功能,比如打印日志。
shutdown():关闭线程池,等待所有任务执行完后再关闭线程池。
package com.demo1.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
public class Demo1 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>()) {
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("执行前调用");
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("执行后调用");
}
@Override
protected void terminated() {
System.out.println("线程池关闭");
}
};
for (int i = 0; i < 3; i++) {
threadPoolExecutor.execute(new PrintCurrentTimeTask());
}
threadPoolExecutor.shutdown();
}
private static class PrintCurrentTimeTask implements Runnable {
@Override
public void run() {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
上面都是使用execute方法进行任务提交,但实际上还可以使用submit进行提交
使用submit会返回一个Future对象,通过该对象我们可以判断任务执行是否成功,并且可以通过Future的get()方法来获取返回值。下面来具体演示submit方法.
get()方法会一直阻塞线程直到执行完毕,支持超时获取。
package com.demo1.thread;
import java.util.concurrent.*;
public class Demo1 {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(new HasReturnTask());
System.out.println(future.get());
Future<String> future1 = executorService.submit(new TimeoutTask());
System.out.println(future1.get(2, TimeUnit.SECONDS));
}
private static class HasReturnTask implements Callable<String> {
@Override
public String call() throws Exception {
return "任务执行完毕";
}
}
private static class TimeoutTask implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "任务执行完毕";
}
}
}
前面提到了使用shutdown来关闭线程池。现在提一下另一个关闭线程池的方法shutdownNow
关闭线程池原理:遍历线程池中的工作线程,逐个调用interrupt中断线程,所以无法响应中断的任务可能永远无法中止。
关闭方法 | 说明 |
---|---|
shutdown | 将线程池状态设置成SHUTDOWN并中断所有空闲状态线程 |
shutdownNow | 将线程池状态设置成STOP,然后停止所有正在执行及空闲状态线程 |
通常调用shutdown方法关闭线程,如果不要求任务全部执行完,也可调用shutdownNow完成关闭。
该段文字摘自JAVA并发编程的艺术
类别 | 详情 |
---|---|
任务(进程)的性质 | CPU密集型任务、IO密集型任务、混合型任务 |
任务的优先级 | 高、中、低 |
任务的执行时间 | 长、中、短 |
任务的依赖性 | 是否依赖其他系统资源,如数据库连接。 |
可以通过Runtime.getRuntime().availableProcessors()获取CPU数
并发编程新手建议入门书籍
Java并发编程的艺术
实战Java高并发程序设计