Java并发编程之Executor框架

1. 概述

java中实现多线程最直接、最基础的做法是实现Runnable接口、继承Thread类和实现Callable接口。对于每一个任务创建一个线程的方式,如果任务数量过多,过度消耗资源,会引起内存溢出的问题(Out of Memory)。

实际上,java线程池的引入也来源于生活中的实际例子。我们去火车站买票,如果每来一人购票,车站就开一窗口,那么很快车站的资源就耗尽了,没有场地,没有资金,去一直开下去。假设车站在平时开设5个窗口,我们称其为核心(core)窗口,那么国庆假期或春运期间,购票人数激增,车站会增加窗口,而受资源限制,车站可将窗口最多增至10个,如果超过10人以上来购票,就需要在窗口按照“先来后到(FIFO)”的方式进行排队等候了。

java自JDK 1.5引入了线程池和Executor框架,至此,开发者就可以像“火车站购票”一样,方便的使用线程池对多线程进行管理了。

2. Executor接口

来自JDK1.5的 java.util.concurrent 包。

void execute(Runnable command)

Executor是一个Functional interface,提供了一个接受Runnable命令的方法。

class ThreadPerTaskExecutor implements Executor {
   public void execute(Runnable r) {
     new Thread(r).start();
   }
 }

可以像上面的例子一样使用Executor,但这不是重点,配合线程池才能发挥其作用。

public class ThreadPoolDemo {
    private static final int NTHREADS = 100;
    private static final Executor es = Executors.newFixedThreadPool(NTHREADS);

    public static void main(String[] args) {
        es.execute(() -> System.out.println("execute task"));
    }
}

Executors.newFixedThreadPool(NTHREADS)创建了一个有NTHREADS数量的线程池。当有新的任务提交时,如果线程池有空余的线程,则立即执行,否则,新的任务会放入队列等待。

3. Executor框架提供的线程池

3.1 ExecutorService接口

public interface ExecutorService extends Executor

Executors接口继承了Executor接口,它提供了shutdown()方法,可以关闭线程池,不再接受新的任务。同时,Executors还增加了submit方法返回一个Future对象,来获取异步任务返回的结果。

<T> Future<T>	submit(Callable<T> task);
Future<?>	submit(Runnable task);
<T> Future<T>	submit(Runnable task, T result);

3.2 JDK提供的线程池

Executors类提供了一些工厂方法创建线程池:
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个线程池,只有固定数量的线程。在任何时候,线程池中最多有nThreads个激活的线程,如果有额外的任务提交,而没有多余的线程可用时,额外的任务就需要在队列中进行等待。如果有线程因为异常退出,则会补充新的线程。
public static ExecutorService newSingleThreadExecutor()
创建只有一个线程的线程池。
public static ExecutorService newCachedThreadPool()
根据需要创建线程。如果线程池中有可复用的线程,就优先使用可复用的线程,否则添加新的线程。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建指定大小的线程池,任务可在指定的延迟时间后执行或周期性的执行。

3.3 自定义线程池

3.2节提到的jdk提供的创建线程池的方法,底层是通过实例化ThreadPoolExecutor去实现的。newFixedThreadPool创建固定数量的线程池,当任务过多时,会造成任务的堆积;newCachedThreadPool根据需要创建,又会造成资源浪费,甚至Out of Memory的问题。一般在项目中更多的是根据需要自定义线程池:

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 - 拒绝策略,当任务过多,来不及执行时的拒绝策略。

备注:ExecutorService 继承了 Executor 接口,ThreadPoolExecutor 实现了 ExecutorService。

4. 一个自定义线程的例子

public class MyThreadPool {
    private static ThreadFactory myThreadPoolFactory =
            new ThreadFactoryBuilder()
                    .setNameFormat("MyThread ThreadFactory-pool")
                    .build();
    private static ThreadPoolExecutor EXECUTOR =
            new ThreadPoolExecutor(
                    12,
                    100,
                    50L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(1024),
                    myThreadPoolFactory,
                    new ThreadPoolExecutor.AbortPolicy());

    private static Future<Integer> fun(int x) {
        return EXECUTOR.submit(() -> x * 2 - 5);
    }

    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 8, 9, 10};
        List<Future<Integer>> futures = Lists.newArrayList();
        Arrays.stream(arr).forEach(x -> futures.add(fun(x)));
        try {
            List<Integer> list = Lists.newArrayList();
            for(Future<Integer> f : futures) {
                list.add(f.get());
            }
            System.out.println(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5. 超载拒绝策略

线程池提供了4种拒绝策略:
static class ThreadPoolExecutor.AbortPolicy
直接抛出一个RejectedExecutionException,阻止系统正常工作。
static class ThreadPoolExecutor.CallerRunsPolicy
只要线程池未关闭,该策略直接在调用者线程中运行被丢弃的任务。
static class ThreadPoolExecutor.DiscardOldestPolicy
该策略丢弃最老的任务,也就是丢弃即将被执行的任务,然后重新提交当前任务。这个策略更看重的是当前提交的任务。“只见新人笑,那关旧人哭。”
static class ThreadPoolExecutor.DiscardPolicy
这个策略则是直接把当前无法执行的任务丢弃了。

附录:多线程中类和接口的继承关系(来源于网络)
继承关系

todo
scheduleExecutorService的例子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值