多线程—线程池Executor框架及四种常用线程池

池化技术应用:线程池、数据库连接池、http连接池等等。

池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

使用线程池的好处:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
  • 管理线程:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。

Executor框架

绿色实线箭头是继承,虚线是接口实现

Executor接口是Executor框架的一个最基本的接口,Executor框架的大部分类都直接或间接地实现了此接口。

Executor接口只有一个execute(Runnable command)方法。

public void execute(Runnable r) {
    new Thread(r).start();
}

ExecutorService接口继承了Executor接口,该接口用于管理线程。

public interface ExecutorService extends Executor {
// 请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。
boolean awaitTermination(long timeout, TimeUnit unit);

// 执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks);

// 执行给定的任务,当所有任务完成或超时期满时,返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);

// 任务列表里只要有一个任务完成了,就立即返回。而且一旦正常或异常返回后,则取消尚未完成的任务。
<T> T invokeAny(Collection<? extends Callable<T>> tasks);

// 执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);

// 如果此执行程序已关闭,则返回 true。
boolean isShutdown();

// 如果关闭后所有任务都已完成,则返回 true。先调用 shutdown 或 shutdownNow,否则 isTerminated 永不为 true。
boolean isTerminated();

// 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
void shutdown();

// 通过调用interrupt试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
List<Runnable> shutdownNow();

// 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future,该Future的get方法在成功完成时将会返回给定的结果。
<T> Future<T> submit(Callable<T> task);

Future<?> submit(Runnable task);

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

execute和submit方法

  • execute,执行一个任务,没有返回值。
  • submit,提交一个线程任务,有返回值。
  • submit(Callable<T> task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。
  • submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。

接下来介绍线程池主要实现类ThreadPoolExecutor

ThreadPoolExecutor构造函数

//五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
//第六个参数
ThreadFactory threadFactory

//第七个参数
RejectedExecutionHandler handler

ThreadPoolExecutor类的构造函数有4个,前面5个参数是必须的,第6和7个参数同这5个参数又构成了3个构造函数。

int corePoolSize

  • 线程池中核心线程数最大值,线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,进入任务队列。
  • 核心线程默认情况下会一直存活在线程池中,即使这个核心线程是闲置状态。如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么闲置状态的核心线程,超过一定时间(keepAliveTime),就会被销毁掉。

int maximumPoolSize

  • 该线程池中线程总数最大值。如果等待队列满了,创建非核心线程。
  • 线程总数 = 核心线程数 + 非核心线程数。

long keepAliveTime

  • 该线程池中非核心线程闲置超时时长。
  • 一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉。
  • 如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。

TimeUnit unit

  • keepAliveTime的时间单位。

BlockingQueue<Runnable> workQueue

  • 该线程池中的任务队列,维护着等待执行的Runnable对象。
  • 当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。

队列的三种通用策略详解:

直接提交 SynchronousQueue

将任务直接提交给线程而不保存它们。如果所有线程都在工作,就新建一个线程来处理这个任务,所以通常要求maximumPoolSizes设置为Integer.MAX_VALUE,即无限大,以避免拒绝新提交的任务。

无界队列 LinkedBlockingQueue

将导致在所有核心线程都在忙时新任务在队列中等待,创建的线程就不会超过 corePoolSize,maximumPoolSize 的值也就没意义了。

有界队列 ArrayBlockingQueue

可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误。

延时队列 DelayQueue

传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务。

ThreadFactory threadFactory

用于创建新线程,是一个接口,new他的时候需要实现他的Thread newThread(Runnable r)方法,一般不用。

RejectedExecutionHandler handler

用于抛出异常,一般不用。

  • ThreadPoolExecutor.AbortPolicy()  抛出java.util.concurrent.RejectedExecutionException异常,默认。
  • ThreadPoolExecutor.CallerRunsPolicy()  重试添加当前的任务,他会自动重复调用execute()方法。 
  • ThreadPoolExecutor.DiscardOldestPolicy()  抛弃旧的任务  
  • ThreadPoolExecutor.DiscardPolicy()  抛弃当前的任务。 

如何配置线程池

CPU密集型任务

CPU核心数+1,CPU 密集型任务使得CPU使用率很高,内存、硬盘、网络占用的时间少于cpu本身计算的时间,这时应配置尽可能小的线程避免线程之间频繁的切换消耗资源。 

IO密集型任务

2*CPU核心数,O密集型任务CPU使用率并不高,当线程发出请求后,由于不占用cpu资源,可以阻塞等待,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。 

混合型任务
可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。

线程池执行策略

  • 线程数量未达到corePoolSize,则新建一个核心线程执行任务
  • 线程数量达到了corePools,则将任务移入队列等待
  • 队列已满,新建线程(非核心线程)执行任务
  • 队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常

常用的四种线程池

CachedThreadPool 缓存线程池

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

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 
    cachedThreadPool.execute(new Runnable(){public void run() {};})

特点:

  • 核心线程数为0,线程数无限制
  • 有空闲线程则复用空闲线程,若无空闲线程则新建线程
  • 空闲线程只会等60s
  • 直接提交队列

FixedThreadPool 定长线程池

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

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int theads);
    fixedThreadPool.execute(new Runnable(){public void run() {};});

 特点:

  • 所有线程都是核心线程,在创建时给定数量
  • 线程空闲就回收
  • 队列无界

SingleThreadExecutor 单线程化的线程池

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    singleThreadExecutor.execute(new Runnable(){public void run() {};});
    static class FinalizableDelegatedExecutorService
        extends DelegatedExecutorService {
        FinalizableDelegatedExecutorService(ExecutorService executor) {
            super(executor);
        }
        protected void finalize() {
            super.shutdown();
        }
    }

加了个finalize方法保证线程池的关闭,DelegatedExecutorService是继承AbstractExecutorService的一个类。

和newFixedThreadPool(1)的区别

封装成FinalizableDelegatedExecutorService类,这个类就是对ExecutorService进行了一个包装,防止暴露出不该被暴露的方法,然后加上了finalize方法保证线程池的关闭。

特点:

  • 线程池只有一个核心线程
  • 线程空闲就回收
  • 队列无界

ScheduledThreadPool 定长周期线程池:

ScheduledThreadPool是一个能实现定时、周期性任务的线程池。

 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int theads);
 scheduledThreadPool.schedule(new Runnable() {
    public void run() {
       System.out.println("延迟1秒执行");
    }
 }, 1, TimeUnit.SECONDS);
 
 scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
    public void run() {
       System.out.println("延迟1秒后每3秒执行一次");
    }
 }, 1, 3, TimeUnit.SECONDS);
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,实现了ScheduledExecutorService接口,该接口定义了schedule等任务调度的方法。ScheduledThreadPoolExecutor有两个重要的内部类:DelayedWorkQueue和ScheduledFutureTask。DelayeddWorkQueue是一个阻塞队列,而ScheduledFutureTask继承自FutureTask,并且实现了Delayed接口。

特点:

  • 给定线程数,定时、周期性处理任务
  • 线程空闲就回收
  • 队列无界

线程池为什么能维持线程不释放,随时运行各种任务?

总结就是:如果队列中没有任务时,核心线程会一直阻塞在获取任务的方法,直到返回任务。

//重点:poll会一直阻塞直到超过keepAliveTime或者获取到任务
//take 会一直阻塞直到获取到任务
//在没有任务的时候 如果没有特别设置allowCoreThreadTimeOut,我们的核心线程会一直阻塞在这里

Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();

所以也解释了workQueues为什么要是BlockingQueue

ArrayBlockingQueue的take方法

 public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;  //加锁
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();  //队列为空时,将使这个线程进入阻塞状态,直到被其他线程唤醒时取出元素
        return dequeue();  //消费对头中的元素
    } finally {
        lock.unlock();
    }
  }

可以看到当队列内没有任务时,调用await方法挂起线程。await方法是ConditionObject的方法,内部调用了LockSupport类的park方法将线程挂起。可以看这里。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章