Java线程池
为什么要使用线程池?
如果使用线程池,频繁的创建和销毁线程会消耗大量系统资源,如果不对线程创建请求进行限制,在并发请求数量非常多,且线程执行时间很短的系统里,可能会造成系统的资源不足;同时,执行相同的任务会重复创建线程,无法重用任务。
使用线程池有什么优点?
- 降低资源消耗,重用任务可以避免重复创建相同任务线程产生的消耗。
- 提高系统响应,任务可以不用执行线程创建的过程便可以执行
- 方便管理线程,线程的执行情况更加容易监控,很容易的知晓当前系统线程的状态。
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设置的边界自动调整池大小。
- 当新任务在方法execute中提交时,如果当前线程池中工作线程少于corePoolSize,则创建新线程来处理请求
- 当新任务在方法execute中提交时,如果当前线程池中工作线程多于 corePoolSize 而少于 maximumPoolSize,则仅当阻塞队列满时才创建新线程。
- 如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。
- 如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。
保持存活时间(keepAliveTime)与其单位(unit)
如果当前线程池中工作线程多于corePoolSize,那么如果线程的空闲时间超过keepAliveTime,则超时的这部分线程将会终结。当然也可以使用setKeepAliveTime()进行动态修改。默认情况下,保持活动策略只在有多于 corePoolSizeThreads 的线程时应用。但是只要 keepAliveTime 值非 0,通过allowCoreThreadTimeOut() 方法也可将此超时策略应用于核心线程。
阻塞队列(BlockingQueue)
用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
- 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
- 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入阻塞队列,而不添加新的线程。
- 如果无法将请求加入阻塞队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
阻塞队列的排队策略
- 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。
- 无界队列。使用无界队列(例如,不具有预定义容量的LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;
- 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。
拒绝执行处理程序(RejectedExecutionHandler)
由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
- 在默认的终止策略ThreadPoolExecutor.AbortPolicy中,处理程序遭到拒绝将抛出运行时RejectedExecutionException。
- 在调用者运行策略ThreadPoolExecutor.CallerRunsPolicy中,线程调用运行该任务的execute()本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
- 在丢弃策略ThreadPoolExecutor.DiscardPolicy中,不能执行的任务将被删除。
- 在丢弃最旧策略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() ,如果可重入,就可能会在对线程池操作的方法中中断线程。