版权声明:如果您觉得此文有用或对您有帮助,请不吝点个赞或留个言,哈哈! https://blog.csdn.net/fanrenxiang/article/details/79855992
引言
java中经常使用线程来执行需要异步的操作,同时使用线程池来解决创建、销毁线程耗费资源的问题,java线程既是工作单元,也是执行机制。从java1.5开始分离工作单元和执行机制,工作单元包括Runnable和Callable,执行机制则由Executor框架提供。
先上代码,再进一步解释所用到的类。如下是一个异步会员充值功能的逻辑
public class ExecutorDemo {
public String ExecutorTest() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool(); //构建线程池
try {
RechargeCallable callable = new RechargeCallable("simonsfan", "MC_segodess2558ssd'"); //构建Callable实现类任务逻辑
FutureTask<CommonResult> futureTask = new FutureTask<CommonResult>(callable); //把Callable实现类赋予FutureTask
executorService.submit(futureTask); //提交任务
if (futureTask.isDone()) {
CommonResult result = futureTask.get(); //get()方法可能会导致阻塞,可用isDone()方法判断
if (result == null) {
return Constants.FAIL;
}
return Constants.SUCCESS;
}
// TODO 其它相关逻辑
return Constants.FAIL;
} finally {
executorService.shutdown(); //关闭线程池资源
}
}
public class RechargeCallable implements Callable<CommonResult> {
private static final Logger log = LoggerFactory.getLogger(RechargeCallable.class);
private String userName;
private String verifyCode;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getVerifyCode() {
return verifyCode;
}
public void setVerifyCode(String verifyCode) {
this.verifyCode = verifyCode;
}
public RechargeCallable(String userName, String verifyCode) {
this.userName = userName;
this.verifyCode = verifyCode;
}
@Override
public CommonResult call() throws Exception {
CommonResult result = null;
try {
result = userChargeService.charge(userName, verifyCode);
return result;
} catch (Exception e) {
Thread.sleep(1000);
result = userChargeService.charge(userName, verifyCode); //重试一次
if (result.getErrorcode() == Constants.FAIL) {
log.error("charge vip exception:{}", e.getMessage());
result.setErrorcode("1");
result.setMessage("charge vip exception");
failRecordService.addInfo(userName, verifyCode, e.getMessage().substring(0, 200));//保存充值失败记录
return result;
}
return result;
}
}
}
Executor框架大致划分
- 任务:包括实现了Callable或Runnable接口的类
- 执行任务:由Executor接口、继承了Executor的ExecutorService接口
- 异步计算结果:主要包含Future接口和实现了Future接口的FutureTask类
核心接口和类
- Executor接口,是Executor框架的基础,它将任务的提交和执行分离开来
- Future接口和实现它的FutureTask类,里面包含了异步计算的结果
- Runnable接口和Callable接口的实现类,表示将要执行的任务,被ThreadPoolExecutor或ScheduledThreadPoolExecutor所执行
- ThreadPoolExecutor类,是线程池的核心实现类,用于执行提交的任务
- ScheduleThreadPoolExecutor类,支持定时执行提交的任务
Executor框架成员
(1)ThreadPoolExecutor
ThreadPoolExecutor可以创建三种类型的线程池,分别为 newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool
newFixedThreadPool
使用固定线程数,适用于为了平衡服务器资源而指定线程数的场景,一般用于负载比较高的服务器
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
newSingleThreadExecutor
使用单个线程数,适用于需要保证顺序的执行各个任务;并且在任意时间点,不会有多个线程活动的场景
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
newCachedThreadPool
使用非固定线程数,适用于执行很多短期的异步任务,或者负载较轻的服务器
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
(2) ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor可以创建出两种类型的线程池 ScheduledThreadPoolExecutor、SingleThreadScheduledExecutor
ScheduledThreadPoolExecutor
包含多个线程,适用于需要多个后台执行周期任务,同时为了满足资源管理的需求而需要限制线程数量的场景
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
SingleThreadScheduledExecutor
只包含单个线程,适用于需要单个后台线程执行周期性任务,同时需要保证顺序的执行各个任务的场景
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
(3)Future接口
Future接口和FutureTask类用来表示异步计算的结果,当向ThreadPoolExecutor或ScheduledThreadPoolExecutor提交了一个Callable或Runnable接口的实现类时,ThreadPoolExecutor或ScheduledThreadPoolExecutor就会返回FutureTask,这里需要注意的是,到目前的jdk为止,submit返回的是实现了Future接口的FutureTask,在未来的java版本中不一定就是FutureTask
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
FutureTask使用场景
1、当一个线程A需要等待另一个线程B执行完后A才能继续执行;
2、多线程执行多个任务时,每个任务最多只能被执行一次;
3、多个线程同时执行一个任务,只能让其中一个线程执行任务,其他线程只能等待这个任务被执行完成后才能继续执行;
如下代码实现:多线程方式提交任务,主线程和子线程各自执行不影响,当主线程需要子线程的结果时,直接异步取出即可
public class FutureTaskTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
try {
CustomCallable callable = new CustomCallable();
Future<Integer> result = executor.submit(callable);
Thread.sleep(2000);
System.out.println("主线程执行自己的任务,同时获取子线程的计算结果="+result.get());
System.out.println("所有任务执行结束");
} finally {
executor.shutdown();
}
}
}
class CustomCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("子线程开始进行计算");
Thread.sleep(500);
int sum = 0;
for (int i = 0; i < 10; i++)
sum += i;
return sum;
}
}
子线程开始进行计算
主线程执行自己的任务,同时获取子线程的计算结果=45
所有任务执行结束
如下代码,在高并发下保证任务只会执行一次(代码取自《Java并发编程的艺术》第十章)
private final ConcurrentMap<Object, Future<String>> taskCache = new ConcurrentHashMap<>();
private String executionTask(final String taskName) {
while (true) {
Future<String> future = taskCache.get(taskName);
if (future == null) {
Callable<String> task = new Callable<String>() {
@Override
public String call() throws Exception {
return taskName;
}
};
FutureTask<String> futureTask = new FutureTask<>(task);
future = taskCache.putIfAbsent(taskName, futureTask);
if (future == null) {
future = futureTask;
futureTask.run();
}
}
try {
return future.get();
} catch (Exception e) {
taskCache.remove(taskName, future);
}
}
}
(4)Callable和Runnable
Callable接口和Runnable接口的实现类(近似等价于被提交任务的逻辑)均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor所执行,区别在于执行的任务逻辑是否需要返回值,Callable接口实现类可以有返回值,而Runnable接口实现类则无;此外可以自定义一个实现了Callable接口的Callable实现类,如开头示例的
RechargeCallable callable = new RechargeCallable("simonsfan", "MC_segodess2558ssd'");
也可以通过Executors类包装的如下两种方式
//此方式创建Callable对象,通过futureTask.get()方法可以获取到异步计算结果
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
//不返回异步计算结果
public static Callable<Object> callable(Runnable task) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<Object>(task, null);
}
注意:
1、通过futureTask.get()方法获取任务计算结果时,当任务还未完成,会导致线程阻塞,一般会配合futureTask.isDone()方法一起使用;
2、提交Callable任务用submit(callable),提交Runnable任务用execute(runnable);
3、当项目中有很多异步任务时,要着重测试下每个异步任务的执行时间,比如某个异步任务是调用其他系统的web服务,这时候就得测试调用需用的时间长短,如果过长,则需要改为生产/消费模式的消息队列去实现,不然很容易让服务器的jvm进程崩溃。
参考资料:《Java并发编程的艺术》