Java线程池

本文介绍了Java线程池的使用原因和优点,详细阐述了Executor框架,包括Executor、ExecutorService、ScheduledExecutorService接口。重点讲解了ThreadPoolExecutor类,涵盖其五种状态、构造方法各参数含义,还介绍了Executors工厂类提供的四种线程池,以及ThreadPoolExecutor内部同步方式。

Java线程池

为什么要使用线程池?

如果使用线程池,频繁的创建和销毁线程会消耗大量系统资源,如果不对线程创建请求进行限制,在并发请求数量非常多,且线程执行时间很短的系统里,可能会造成系统的资源不足;同时,执行相同的任务会重复创建线程,无法重用任务。

使用线程池有什么优点?

  1. 降低资源消耗,重用任务可以避免重复创建相同任务线程产生的消耗。
  2. 提高系统响应,任务可以不用执行线程创建的过程便可以执行
  3. 方便管理线程,线程的执行情况更加容易监控,很容易的知晓当前系统线程的状态。

Executor框架

框架类图:

Executor接口

Executor是Java语言提供的,用于管理任务的工具。它只有一个方法execute(Runnable command),作用是提交一个将要执行的任务,该命令可能在新的线程、已入池的线程或者正调用的线程等中执行,这取决于 Executor的实现。

一个简单的例子:

public class ExecutorTest implements Executor {  //实现Executor接口
    @Override
    public void execute(Runnable command) {
        new Thread(command).start();
    }
}
public class Main {
    public static void main(String[] args) {
        ExecutorTest executorTest=new ExecutorTest();
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("NewThread");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        executorTest.execute(runnable);  //提交任务
    }
}

ExecutorService接口

ExecutorService继承并扩展了Executor接口,提供了停止接受任务(shutdown)、停止所有任务(shutdownNow)、有返回结果的任务(submit)等方法,可以更好地管理任务。

ScheduledExecutorService接口

扩展了ExecutorService接口,添加了延时执行和定时执行的方法。

ThreadPoolExecutor类

线程池有一下五种状态

    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
  • **RUNNING:**正常运行
  • **SHUTDOWN:**在执行了shutdown()后进入该状态,启动有序关闭,已经提交的任务将被执行,不再接受新提交的任务
  • **STOP:**在执行了shutdownNow()后进入该状态,会试图中断(不一定成功)正在运行的任务,并停止等待任务的处理,典型的线程池实现由Thread.interrupt()实现,所以无法响应中断的线程永远不会停止
  • **TIDYING:**如果所有任务停止且工作线程数为0,则进入该状态时调用terminated()
  • **TERMINATED:**调用terminated()进入该状态,这个状态什么也不做
ThreadPoolExecutor的构造方法
 public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

核心线程池大小(corePoolSize)与最大线程池大小(maximumPoolSize)

ThreadPoolExecutor根据corePoolSize和maximumPoolSize设置的边界自动调整池大小。

  1. 当新任务在方法execute中提交时,如果当前线程池中工作线程少于corePoolSize,则创建新线程来处理请求
  2. 当新任务在方法execute中提交时,如果当前线程池中工作线程多于 corePoolSize 而少于 maximumPoolSize,则仅当阻塞队列满时才创建新线程。
  3. 如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。
  4. 如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。

保持存活时间(keepAliveTime)与其单位(unit)

如果当前线程池中工作线程多于corePoolSize,那么如果线程的空闲时间超过keepAliveTime,则超时的这部分线程将会终结。当然也可以使用setKeepAliveTime()进行动态修改。默认情况下,保持活动策略只在有多于 corePoolSizeThreads 的线程时应用。但是只要 keepAliveTime 值非 0,通过allowCoreThreadTimeOut() 方法也可将此超时策略应用于核心线程。

阻塞队列(BlockingQueue)

用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

  1. 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
  2. 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入阻塞队列,而不添加新的线程。
  3. 如果无法将请求加入阻塞队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

阻塞队列的排队策略

  1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。
  2. 无界队列。使用无界队列(例如,不具有预定义容量的LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;
  3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。

拒绝执行处理程序(RejectedExecutionHandler)

由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

  1. 在默认的终止策略ThreadPoolExecutor.AbortPolicy中,处理程序遭到拒绝将抛出运行时RejectedExecutionException。
  2. 在调用者运行策略ThreadPoolExecutor.CallerRunsPolicy中,线程调用运行该任务的execute()本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
  3. 在丢弃策略ThreadPoolExecutor.DiscardPolicy中,不能执行的任务将被删除。
  4. 在丢弃最旧策略ThreadPoolExecutor.DiscardOldestPolicy中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

线程工厂(ThreadFactory)

执行程序创建新线程时使用的工厂类,一个最简单的实现:

public class SimpleThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r);
    }
}

Executors工厂类

我们通常不直接使用ThreadPoolExecutor,而是用Executors类中提供的四种线程池:newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool。

newFixedThreadPool

创建一个固定线程数的线程池,以无界队列方式来运行这些线程。

newSingleThreadExecutor

创建一个固定线程数为1的线程池,以无界队列方式来运行这些线程,运行顺序遵守线程优先级队列规则。

newCachedThreadPool

创建一个可根据需要创建新线程的线程池,在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。

newScheduledThreadPool

创建一个线程池,它会按规定时间延迟执行或定期执行。

ThreadPoolExecutor内部实现同步的方式

线程池中的线程被封装成为一个Worker对象,它继承自AQS并实现了Runnable接口。Worker使用AQS来实现不可重入的独占锁。lock方法一旦获取了独占锁,表示线程正在执行任务,所以不是独占锁的状态就代表线程空闲。

不可重入是因为不想让在调用比如setCorePoolSize()等线程池控制方法时可以再次获取锁,setCorePoolSize()时可能会interruptIdleWorkers(),在对一个线程interrupt时会要w.tryLock() ,如果可重入,就可能会在对线程池操作的方法中中断线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值