并发编程之Executor框架(线程池、异步任务)

版权声明:如果您觉得此文有用或对您有帮助,请不吝点个赞或留个言,哈哈! 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并发编程的艺术》

猜你喜欢

转载自blog.csdn.net/fanrenxiang/article/details/79855992