ExecutorService学习

37 篇文章 4 订阅
32 篇文章 1 订阅

目录

介绍

​可缓存线程池

单线程池

固定线程数线程池

固定线程数,支持定时和周期性任务

单线程执行器

手动创建线程池

ForkJoinPool

ExecutorService的关闭


介绍

在我们的日常开发中,难免会使用到线程,部分还会用到多线程并发问题。我们知道,线程的创建和释放,需要占用不小的内存和资源。如果每次需要使用线程时,都new 一个Thread的话,难免会造成资源的浪费,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。不利于扩展,比如如定时执行、定期执行、线程中断,所以很有必要了解下ExecutorService的使用。它继承Executor 接口。Executors是工具类。

        ExecutorService是Java提供的线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了.

ExecutorService的创建方式
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
所有线程池最终都是通过这个方法来创建的。

corePoolSize : 核心线程数,一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程都处于激活状态时,任务将被挂起,等待空闲线程来执行。

maximumPoolSize : 最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程;如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。

keepAliveTime : 也就是当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁,但只针对于非核心线程。

unit : 时间单位,TimeUnit.SECONDS等。

workQueue : 任务队列,存储暂时无法执行的任务,等待空闲线程来执行任务。

threadFactory :  线程工厂,用于创建线程。

handler : 当线程边界和队列容量已经达到最大时或关闭时,用于处理阻塞时的程序

线程池的类型


可缓存线程池

ExecutorService cachePool = Executors.newCachedThreadPool();看看它的具体创建方式:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    通过它的创建方式可以知道,创建的都是非核心线程,而且最大线程数为Interge的最大值,空闲线程存活时间是1分钟。如果有大量耗时的任务,则不适该创建方式。它只适用于生命周期短的任务。

优点:灵活回收和创建空闲线程

缺点:最大线程数为Integer.MAX_VALUE,容易造成堆外内存溢出。

单线程池

ExecutorService singlePool = Executors.newSingleThreadExecutor();
顾名思义,也就是创建一个核心线程: 。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
优点:只用一个线程来执行任务,保证任务按FIFO顺序一个个执行,当线程执行中出现异常,去创建一个新的线程替换之。

缺点:并发性能不好。


固定线程数线程池

也就是创建固定数量的可复用的线程数,来执行任务,当线程数达到最大核心线程数,则加入队列等待有空闲线程时再执行。
Executors.newFixedThreadPool(3);
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

优点:FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。
缺点:如果线程遇到错误中止,它是无法使用替代线程的。

固定线程数,支持定时和周期性任务

最大线程数依然是 Integer.MAX_VALUE。
ExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}
可用于替代Timer定时器等延时和周期性任务。与Timer 对比:Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务(比如:一个任务出错,以后的任务都无法继续)。

public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
scheduleAtFixedRate和sheduleWithFixedDelay有什么不同呢?

scheduleAtFixedRate:创建并执行一个在给定初始延迟后的定期操作,也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后下一个任务执行,接着在 initialDelay + 2 * period 后执行,依此类推 ,也就是只在第一次任务执行时有延时。

sheduleWithFixedDelay:创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟,即总时间是(initialDelay +  period)*n

单线程执行器

newSingleThreadScheduledExecutor 创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行。当线程执行中出现异常,不会去创建一个新的线程替换之。或者被try...catch后 继续运行

   public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

手动创建线程池

private ExecutorService pool = new ThreadPoolExecutor(3, 10,
            10L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(512), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
可以根据自己的需求创建指定的核心线程数和总线程数。

ForkJoinPool

newWorkStealingPool :newWorkStealingPool适合使用在很耗时的操作,但是newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展。但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中,实际上是new了一个ForkJoinPool。

    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }


ExecutorService 中submit和Excutor 中execute的区别:ExecutorService继承自Executor,下面是submit的操作,其实都是new一个FutureTask对象,FutureTask里有一个Callable字段,执行的时候执行对应的call方法。但是当提交Runnable任务的时候会通过Executors生成一个Callable的子类对象。RunnableAdapter,这个类是Executors的内部类,运用了适配器的设计模式融合了Callable和Runnable。

    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

    private static final class RunnableAdapter<T> implements Callable<T> {
        private final Runnable task;
        private final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
        public String toString() {
            return super.toString() + "[Wrapped task = " + task + "]";
        }
    }

1.如果提交的任务不需要一个结果的话直接用execute()会提升很多性能

2.就是相当于说如果你传的任务是需要结果的,那你就使用你的类去继承Callable接口,然后告诉submit方法就行了,如果你想要执行成功后返回自己定义的特定结果,就把那个特定的结果也告诉submit方法。注意:带T的只能是Runable的,并且Future的get方法返回的是你输入进入的结果,也就是result。

如果你不需要一个结果,那么就老老实实使用execute,如果你需要的是一个空结果,那么submit(yourRunnable)与submit(yourRunnable,null)是等价的!最重要的是,submit提交的任务异常时被task捕获的,所以要通过task的get来获取异常,否则异常会被线程池吞掉,而execute的异常不会被吞掉。

ExecutorService的关闭

shutdown和awaitTermination为接口ExecutorService定义的两个方法,一般情况配合使用来关闭线程池。

taskExecutor.shutdown();
 while (!taskExecutor.awaitTermination(1, TimeUnit.SECONDS));

shutdown方法:平滑的关闭ExecutorService,当此方法被调用时,ExecutorService停止接收新的任务并且等待已经提交的任务(包含提交正在执行和提交未执行)执行完成。当所有提交任务执行完毕,线程池即被关闭。

awaitTermination方法:当前线程阻塞,直到返回true或者false,接收人timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用,只调用awaitTermination会等所有已提交的任务(包括正在跑的和队列中等待的)执行完,可以继续提交。

shutdownNow方法:将线程池状态置为STOP。企图立即停止,事实上不一定:跟shutdown()一样,先停止接收外部提交的任务,
忽略队列里等待的任务,尝试将正在跑的任务interrupt中断,返回未执行的任务列表,它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有处理interruptFlag,interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。但是大多数时候是能立即退出的。
shutdown()和shutdownNow()的区别:

从字面意思就能理解,shutdownNow()能立即停止线程池,正在跑的和正在等待的任务都停下了。这样做立即生效,但是风险也比较大; shutdown()只是关闭了提交通道,用submit()是无效的;而内部该怎么跑还是怎么跑,跑完再停。

shutdown()和awaitTermination()的区别

shutdown()后,不能再提交新的任务进去;但是awaitTermination()后,可以继续提交。 awaitTermination()是阻塞的,返回结果是线程池是否已停止(true/false);shutdown()不阻塞。

isShutdown() 表示此线程池是否关闭。一般在提交任务前执行来判断线程池状态。

线程池原理:

        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {//核心线程是否满了
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {//放入队列
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))//没超过最大线程会创建线程执行
            reject(command);//执行失败handler
    }
 private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
//这块代码是在创建非核心线程时,即core等于false。
//判断当前线程数是否大于等于maximumPoolSize,如果大于等于则返回false
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);//创建Worker对象,同时也会实例化一个Thread对象
            //worker实现了runnable
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();//启动启动这个线程,并调用work里的run方法。
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
            //如果task 不为null或者getTask方法从workerQueue里读取任务不为null
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();//执行任务
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
 private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //判断当前线程数是否大于corePoolSize。
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
//如果当前线程数大于核心线程数,则会调用workQueue的poll方法获取任务,
//超时时间是keepAliveTime。如果超过keepAliveTime时长,poll返回了null,
//上边提到的while循序就会退出,线程也就执行完了。
//如果当前线程数小于corePoolSize,则会调用workQueue的take方法阻塞在当前
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

注意runWorker里while循环,如果参数task执行完毕后还会通过getTask方法判断队列是否有任务,如果有就执行。没有就break推出循环。

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor的主要特点是两个内部类ScheduledFutueTask和DelayedWorkQueue,实际上这也是线程池工作流程中最重要的两个关键因素:任务以及阻塞队列。现在我们来看下ScheduledThreadPoolExecutor提交一个任务后,整体的执行过程。以ScheduledThreadPoolExecutor的schedule方法为例,具体源码为:

    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay,
                                           TimeUnit unit) {
        if (callable == null || unit == null)
            throw new NullPointerException();
        //将提交的任务转换成ScheduledFutureTask
        RunnableScheduledFuture<V> t = decorateTask(callable,
            new ScheduledFutureTask<V>(callable,
                                       triggerTime(delay, unit)));
        //延时执行任务ScheduledFutureTask
        delayedExecute(t);
        return t;
    }
private void delayedExecute(RunnableScheduledFuture<?> task) {
    if (isShutdown())
        //如果当前线程池已经关闭,则拒绝任务
        reject(task);
    else {
        //将任务放入阻塞队列中
        super.getQueue().add(task);这个Queue就是我们刚才看到的DelayedWorkQueue
        if (isShutdown() &&
            !canRunInCurrentRunState(task.isPeriodic()) &&
            remove(task))
            task.cancel(false);
        else
            //保证至少有一个线程启动,即使corePoolSize=0
            ensurePrestart();
    }
}

void ensurePrestart() {
    int wc = workerCountOf(ctl.get());
    if (wc < corePoolSize)
        addWorker(null, true);
    else if (wc == 0)
        addWorker(null, false);
}

        public boolean offer(Runnable x) {
            if (x == null)
                throw new NullPointerException();
            RunnableScheduledFuture e = (RunnableScheduledFuture)x;
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                int i = size;
                if (i >= queue.length)
                    grow();//扩容
                size = i + 1;
                if (i == 0) {//如果是第一个元素
                    queue[0] = e;
                    setIndex(e, 0);
                } else {//如果不是,那么进行上滤操作,保证堆有序
                    siftUp(i, e);
                }
                //如果当前新增加的元素在堆顶,那么可能是最新要执行的
                if (queue[0] == e) {
                    leader = null;
                    available.signal();//发一个信号,通知take的时候的线程,赶紧检测新加入的task
                }
            } finally {
                lock.unlock();
            }
            return true;
        }

这是DelayedWorkQueue的offer方法,DelayedWorkQueue里面是使用数组去维护任务队列的,那么数组是怎么保证任务有序呢?
其实仔细看代码,我们能发现,这里的实现是用一个二叉堆去对数组元素进行排序。确切的说是小顶堆。
首先判断容量,如果容量不够就扩容,接着判断是不是第一个元素,如果是,那么直接放在index为0的位置,不是的话进行上滤操作。接下来判断添加的元素是不是在堆顶,如果是那么需要进行优先调度,那么进行signal。
其实这里又引申出一个问题,那么就是是靠什么排序的?这个时候我们看一下任务的实体ScheduledFutureTask,它复写了compareTo方法

public int compareTo(Delayed other) {
            if (other == this) // compare zero if same object
                return 0;
            if (other instanceof ScheduledFutureTask) {
                ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
                long diff = time - x.time;//根据time去比较,time是在创建任务的时候计算出来的,指下一次运行的时间
                if (diff < 0)
                    return -1;
                else if (diff > 0)
                    return 1;
                else if (sequenceNumber < x.sequenceNumber)
                    return -1;
                else
                    return 1;
            }
            long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
            return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
        }

里面用time去判断大小,time便是下一次调度的时间点,那么显然越小的离现在越近,越要放在前面。
看了offer方法我们再看看take方法,这个方法是用来获取任务的:

 public RunnableScheduledFuture<?> take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();//阻塞
            //队列的存储采用数组,优先级排序采用二叉堆实现。
            try {
                for (;;) {
                    //第一个是最先应该被执行的
                    RunnableScheduledFuture<?> first = queue[0];
                    if (first == null)
                        available.await();
                    else {
                    //获取第一个任务,是距离当前最近的任务,下面获取延迟
                        long delay = first.getDelay(NANOSECONDS);
                        if (delay <= 0)//要立即执行
                            return finishPoll(first);
                        first = null; // don't retain ref while waiting
                        if (leader != null)
                            available.await();//拿不到leader的线程全部await
                        else {
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                            //如果此时线程唤醒了,那么其他线程将不能进入同步块//
                                available.awaitNanos(delay);//等待延迟时间过去
                            } finally {
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                if (leader == null && queue[0] != null)
                    available.signal();//唤醒所有的await线程
                lock.unlock();
            }
        }

毫无疑问,take中直接获取queue[0],它是距离目前最近的要被执行的任务,先检测一下还有多长时间,任务会被执行,如果小于0,那么立刻弹出,并且做一个下滤操作,重新找出堆顶元素。如果不小于0,那么证明时间还没到,那么available.awaitNanos(delay);等到delay时间后自动唤醒,或者因为添加了一个更加紧急的任务即offer中的signal被调用了,那么唤醒,重新循环获取最优先执行的任务,如果delay小于0,那么直接弹出任务。
至此任务调度的逻辑分析完了,但是还有周期执行是怎么实现的呢?其实是在ScheduledFutureTask的run中实现的:

  public void run() {
            boolean periodic = isPeriodic();//判断是不是定时周期调度的
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {执行后futureTask状态设置为new
                setNextRunTime();//计算下一次执行时期
                reExecutePeriodic(outerTask);//加入队列
            }
        }

判断是不是周期调度的任务,如果是等待执行完毕之后,重新设置下一次执行时间,并且将此任务重新offer到queue中,这样就实现了周期调度。

问题:
其实这里是有一个问题的,就是如果当核心线程池比较少,但是执行的任务又有很多阻塞性的任务,那么就会导致在任务到期该执行的时候,而没有线程去执行任务。这样就会导致任务调度时间不准,同时后面的任务也可能被影响,所以在设置的时候可以将自己的核心线程池调大一点,避免这种问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值