java多线程学习笔记(4)

java多线程学习笔记(4) -线程池、Executor

12、线程池

  • 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
  • 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回线程池中
  • 好处
    • 提高响应速度(少了创建新线程的时间)
    • 降低了资源消耗(重复利用率线程池中的线程)
    • 便于线程管理(corePoolSize-核心池大小,maximumPoolSize-最大线程数,keepAliveTime-线程没有任务时最多保持多久时间后终止)

1、Executor框架

在java中,使用线程来执行异步任务时,线程的创建和销毁需要一定的开销,如果我们为每一个任务创建一个新的线程来执行的话,那么这些线程的创建与销毁将消耗大量的计算资源。同时为每一个任务创建一个新线程来执行,这样的方式可能会使处于高负荷状态的应用最终崩溃。

我们在线程池中创建若干条线程,当有任务需要执行时就从该线程池中获取一条线程来执行任务,如果一时间任务过多,超出线程池的线程数量,那么后面的线程任务就进入一个等待队列进行等待,直到线程池有线程处于空闲时才从等待队列获取要执行的任务进行处理,以此循环…这样就大大减少了线程创建和销毁的开销,也会缓解应用处于超负荷时的情况。

Executor框架包括3部分:1.任务:包括被执行任务需要实现的接口:Runnable接口或Callable接口;2.任务的执行:包括任务执行机制的核心接口Executor,以及继承自Executor的EexcutorService接口。Exrcutor有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。3.异步计算的结果:包括接口Future和实现Future接口的FutureTask类。

相关接口和类图:

ScheduledThreadPoolExecutor

执行使用流程一般为:

Executor

  1. Executor
//Executor只有一个方法execute
void execute(Runnable command);
  1. ExecutorService

ExecutorService是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法。

//启动有序关闭,其中执行先前提交的任务,但不会接受新任务
void shutdown();
//尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。
List<Runnable> shutdownNow();
boolean isShutdown();
//如果所有任务在关闭后都已完成,则返回true 。注意除非首先调用了shutdown或shutdownNow ,否则isTerminated永远不会为true 。
boolean isTerminated();
//在关闭请求后阻塞,直到所有任务都完成执行,或者发生超时,或者当前线程被中断,以先发生者为准。
//参数:timeout – 等待的最长时间 unit – 超时参数的时间单位
boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
//提交一个返回值的任务以供执行,并返回一个表示该任务待处理结果的 Future。
<T> Future<T> submit(Callable<T> task);
//提交 Runnable 任务以执行并返回代表该任务的 Future。 Future 的get方法将在成功完成后返回给定的结果,即result。如果result参数为null,返回的 Future 的get方法将在成功完成后返回null 
<T> Future<T> submit(Runnable task, T result);
//执行给定的任务,返回一个 Futures 列表,在所有完成时(或者超时)保存它们的状态和结果。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
//执行给定任务,返回已成功完成的任务的结果(即,不抛出异常),如果有的话。正常或异常返回时,取消未完成的任务。
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
  1. Executors工具类

Executors类提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。因为ThreadPoolExecutor的参数众多且意义重大,为了避免配置出错,才有了Executors工厂类。

最常使用的方法有:

//任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
//创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
//创建一个单例线程,任意时间池中只能有一个线程
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
//创建一个支持定时及周期性的任务执行的线程池,可以安排命令在给定延迟后运行,或定期执行
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}
//Runnable转化为Callable对象,该对象在被调用时运行给定的任务并返回给定的结果。,如果result参数为null,则返回Object为null的对应的Callable<Object>。
public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}

2、ThreadPoolExecutor

ThreadPoolExecutor一般通过工具类Executor创建,其构造函数为:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
//最后两个参数为可选参数,不提供则默认为Executors.defaultThreadFactory()和defaultHandler
/**
参数:
corePoolSize - 保留在池中的线程数,即使它们是空闲的,核心线程会一直在线程池中存活。除非设置allowCoreThreadTimeOut为true ,那么闲置的核心线程在等待新任务到来时会执行超时策略,这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。
maximumPoolSize – 池中允许的最大线程数,超过则后续的新任务将会被阻塞。 
keepAliveTime – 当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最长时间。 超过这个时长,非核心线程就会被回收。
unit – keepAliveTime参数的时间单位 
workQueue – 用于在执行任务之前保存任务的队列。此队列将仅保存由execute方法提交的Runnable任务   
threadFactory – 执行器创建新线程时使用的工厂 
handler – 饱和策略,由于达到线程边界和队列容量而阻塞执行时使用的处理程序。当等待队列已满,线程数也达到最大线程数时,线程池会根据饱和策略来执行后续操作,默认的策略是抛弃要加入的任务。
*/

ThreadPoolExecutor执行任务时的大致规则:

  1. 当有新任务来的时候,先看看当前的线程数有没有超过核心线程数,如果没超过就直接新建一个线程(核心线程)来执行新的任务.
  2. 如果超过了则查看缓存队列有没有满,没满就将新任务放进缓存队列中,
  3. 满了,如果线程数量未达到线程池规定的最大值,就新建一个线程(非核心线程)来执行新的任务,
  4. 如果线程池中的线程数已经达到了指定的最大线程数了,那就根据相应的策略拒绝任务。
  5. 当缓存队列中的任务都执行完了的时候,线程池中的线程数如果大于核心线程数,就销毁多出来的线程,直到线程池中的线程数等于核心线程数。此时这些线程就不会被销毁了,它们一直处于阻塞状态,等待新的任务到来。
2.1 SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

SingleThreadExecutor是只有一个线程的线程池,常用于需要让线程顺序执行,并且在任意时间,只能有一个任务被执行,而不能有多个线程同时执行的情况。

阻塞队列是无限的双向队列,因此如果没有调用shutDownNow()或者shutDown()方法,线程池不会拒绝任务,也因此不会新建非核心线程,maximumPoolSize,keepAliveTime都是无效的。

2.2 FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}

FixedThreadPool表示线程数量固定的线程池,一般是为了满足资源管理的需求,而需要适当限制当前线程数量的情景,适用于负载比较重的服务器。

它的实现就是把线程池最大线程数量maxmumPoolSize和核心线程池的数量corePoolSize设置为相等。并且其使用LinkedBlockingQueue作为阻塞队列,任务到来时要么启用空闲的核心线程,要么加入阻塞队列,同样不会新建核心线程。

2.3 CachedThreadPool
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,  new SynchronousQueue<Runnable>());
}

CachedThreadPool适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。

其使用不存储元素的阻塞队列SynchronizedQueue,也就是说,每次向队列中put一个任务必须等有线程来take这个任务。但其 maximumPoolSize是无界的,也就是意味着如果主线程提交任务的速度高于 maximumPoolSize中线程处理任务的速度时 CachedThreadPool将会不断的创建新的线程。

如果当前线程执行完任务则循环从阻塞队列中获取任务,如果当前队列中没有提交(offer)任务,那么线程等待keepAliveTime时间,在CacheThreadPool中为60秒,在keepAliveTime时间内如果有任务提交则获取并执行任务,如果没有则销毁线程,因此最后如果一直没有任务提交了,线程池中的线程数量最终为0。

3、ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor类继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口。主要用于延时执行任务或者定期执行任务。

3.1 ScheduledExecutorService接口

继承自ExecutorService,新增4个方法

//提交在给定延迟后启用的一次性任务
public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
//提交在给定延迟后启用的返回值的一次性任务。
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
//提交在给定初始延迟后首先启用的周期性操作,随后在给定周期内启用;也就是说,执行将在initialDelay之后开始,然后是initialDelay + period ,然后是initialDelay + 2 * period ,依此类推。任务执行将持续到(1)该任务通过返回的Future显式取消。(2)executor终止(3)执行引发异常
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,long period, TimeUnit unit);
//提交一个周期性操作,该操作首先在给定的初始延迟之后启用,随后在一个执行的终止和下一个执行的开始之间具有给定的延迟。delay参数即为一个执行的终止和下一个执行的开始之间的延迟。任务的执行终止条件与scheduleAtFixRate一致
public ScheduledFuture<?>scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
3.2 创建

其构造方法是直接调用父类ThreadPoolExcutor的构造方法,只不过需要的参数更少,皆为其默认参数,因为使用的DelayQueue是一个无界队列,所以maximumPoolSize以及keepAliveTime, unit,在 ScheduledThreadPoolExecutor中无意义。

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize,Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue());
}
//ThreadFactory,RejectedExecutionHandler为可选参数
public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory,RejectedExecutionHandler handler)

一般情况下利用Executors工厂类来创建两种ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor和SingleThreadScheduledExecutor.

Executors.newScheduledThreadPool(int threadNums);
Executors.newScheduledThreadPool(int threadNums, ThreadFactory threadFactory);
//SingleThreadScheduledExecutor:适用于需要单个线程延时或者定期的执行任务,同时需要保证各个任务顺序执行的应用场景。
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
      return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}
Executors.newSingleThreadScheduledExecutor(int threadNums, ThreadFactory threadFactory);
3.3 实现

ScheduledThreadPoolExecutor通过它的scheduledAtFixedTime()方法或者scheduledWithFixedDelay()方法向阻塞队列添加一个实现了RunnableScheduledFutureTask接口的ScheduledFutureTask类对象。

ScheduledFutureTask主要包括3个成员变量:

private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> 
//序列号,用于保存任务添加到阻塞队列的顺序;
        private final long sequenceNumber;
//保存该任务将要被执行的具体时间;
        private volatile long time;
//重复任务的周期,以纳秒为单位/
        private final long period;

ScheduledTreadPoolExecutor的阻塞队列是用DelayQueue实现的,可以实现元素延时delayTime后才能获取元素,DelayQueue内部封装了一个PriorityQueue,来对任务进行排序,首先对time排序,time小的在前,如果time一样,则sequence小的在前,也就是说如果time一样,那么先被提交的任务先执行。

其流程为:

  1. 调用 ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向 DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduleFutureTask。
  2. 线程池中的线程从DelayQueue中获取 当前时间大于time的ScheduleFutureTask,然后执行任务。
3.4使用

scheduleAtFixedRate、`scheduleWithFixedDelay使用示例

public class TestScheduleExecutor  implements Runnable{
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"   开始时间  "+new Date(System.currentTimeMillis()));
        try{
            Thread.sleep(5000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"   结束时间  "+new Date(System.currentTimeMillis()));
    }
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor ste = new ScheduledThreadPoolExecutor(5);
        for (int i = 0; i < 5; i++) {
//            ste.scheduleAtFixedRate(new TestScheduleExecutor(), 0, 10, TimeUnit.SECONDS);
            ste.scheduleWithFixedDelay(new TestScheduleExecutor(), 0, 5, TimeUnit.SECONDS);
        }
    }
}

除此之外,还有另一个核心方法schedule 方法,其接受任务、延迟时间,能够延迟执行任务:

    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        // 包装任务
        RunnableScheduledFuture<Void> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,triggerTime(delay, unit),sequencer.getAndIncrement()));
        // 延迟执行任务
        delayedExecute(t);
        return t;
    }
private void delayedExecute(RunnableScheduledFuture<?> task) {
        // 如果线程池已关闭,执行拒绝策略
        if (isShutdown())
            reject(task);
        else {
            // 将任务入队
            super.getQueue().add(task);
            // 如果该任务是非周期任务, 则将其从队列移除
            if (!canRunInCurrentRunState(task) && remove(task))
                // 取消任务
                task.cancel(false);
            else
                // 添加一个工作线程
                ensurePrestart();
        }
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值