专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java线程池详解(java线程池运行原理)

temp10 2024-09-19 04:01:29 java教程 14 ℃ 0 评论

我们都知道线程的创建和销毁都是会消耗大量的资源。在大批量并发的场景下,频繁地创建和销毁线程会严重影响系统的性能。所以,通常情况下需要预先创建好几个线程,并且将其集中管理起来,形成一个线程池。在需要用到线程执行的时候,可以从线程池中拿到一个线程直接进行使用,线程的管理则是交给了线程池来管理,这样就可以减少创建和销毁线程所带来的性能影响。

Executors

从Java5开始,JDK提供了Executors来进行线程池的创建;下面就来介绍一下Java为我们提供的线程池都有哪些?

Java线程池详解(java线程池运行原理)

  • 创建固定大小的线程池;
ExecutorService cachePool = Executors.newFixedThreadPool(20);

上面这段代码是创建了一个20个固定线程大小的线程池。也就是说这个线程池同时可以运行20个线程。如果超过20个线程同时访问的时候,就会进入到等待队列中,等待线程执行完毕之后,线程池中有空闲线程然后再依次执行线程任务。其优点就是减少了创建线程所带来的开销,缺点则是线程池中的线程数量被限制了,当并发数太高的话会影响整体的系统性能。

  • 创建可变大小的线程池
ExecutorService executorService = Executors.newCachedThreadPool();

上面代码创建了一个可以缓存空闲线程60秒的线程池。当有新的线程进入的时候,如果线程池中没有空闲线程可以使用,则创建一个新的线程并且添加到线程池中。这个线程池的大小就是Integer.MAX_VALUE。如果线程超过60秒之内没有执行线程这个线程就会被销毁。这种线程池的优点就是可以根据需求来创建线程,并且对线程进行回收。但是缺点也比较明显,如果线程量超过限制的话就会导致宕机等问题的发生。

  • 创建只有一个线程的线程池
ExecutorService oneThreadPool = Executors.newSingleThreadExecutor();
  • 创建一个支持定时或者周期性执行任务的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

ExecutorService

从上面代码中可以看到,通过Executors 创建的线程池都是实现了ExecutorService接口,我们可以通过调用它的execute()方法或者是submit()方法往线程池中提交任务,以便线程池执行完成任务的执行。

下面我们就来介绍一下ExecutorService接口中中的主要方法

  • shutdown():异步关闭线程池方法,调用之后,就不能再向线程池中提交线程任务了,但是允许线程池中没有执行完成的任务继续执行。该方法调用之后立即返回,不会阻塞当前线程。
  • shutdownNow():立即关闭线程池,调用之后,不但不能往线程池中提交任务 ,而且会取消已经提交到线程等待队列中的线程任务,并且线程池中的所有线程都将会被调用interrupt()方法来尝试中断正在执行中的线程任务。调用之后立即返回结果,不阻塞当前线程。但是由于不能优雅的中断线程,所以也会带来很多意想不到的问题,建议慎用。
  • boolean awaitTermination(long timeout, TimeUnit unit):调用这个方法将阻塞当前线程,等待线程池中的任务执行完毕,参数设置为阻塞的时间,以及单位。
  • boolean isTerminated():调用了shutdown()方法之后用来判断任务是否已经全部完成。
  • submit( task):向线程池中提交一个任务,并且需要线程池的反馈结果。
  • void execute(Runnable command):线程池中提交任务,并且不需要线程反馈结果
  • boolean isShutdown():返回线程池是否已经关闭
  • invokeAll():启动多个线程,在需要并发执行多个任务的情况下进行使用。这些任务要么执行成功,要么因为异常或者是超时被取消。
  • invokeAny():启动多个线程,并且相互之间独立的计算一个结果,一旦其中的一个执行成功,就会终止其他线程的执行。

ThreadPoolExecutor

追踪代码会发现,Executors 调用的创建线程的方法内部都是通过调用了ThreadPoolExecutor来实现的。如下图所示。

因此,我们也可以直接通过ThreadPoolExecutor来构建需要的线程池,但是需要了解其中的参数的使用。具体内容如下

  • private volatile int corePoolSize; 核心线程数,也就是线程池中最少支持执行的线程数。
  • private volatile int maximumPoolSize:最大线程数
  • private volatile long keepAliveTime :线程最长空闲时间
  • private final BlockingQueue<Runnable> workQueue; 任务队列,不同类型的线程采用的任务队列是不一样的,比较常用的有SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue。
  • private volatile ThreadFactory threadFactory;用于确定怎么去创建线程池,一般用来确定是否为守护线程,并且设置线程分组、名称、优先级等等。
  • private volatile RejectedExecutionHandler handler;拒绝策略,当线程池等待队列达到上限之后,需要提供什么样的处理机制。

ThreadPoolExecutor 使用规则是比较复杂的,如果不能正确地掌握就会带来各种各样的麻烦,简单的可以归纳如下

  • 在执行线程数低于核心线程数的情况下来进行线程的创建
  • 线程数高于核心线程数的且低于最大线程数的时候,启动线程等待队列来处理线程等待。这个时候就需要更具具体使用的线程队列来判断,如果使用的SynchronousQueue 队列则检查是否有空闲线程,有空闲线程则使用空闲线程来执行任务,反之就需要创建新的线程来执行。如果使用的是LinkedBlockingQueue 队列,新任务就会进入到线程队列进行等待执行操作。这就意味着最大线程数在这种情况下是无效的。因为线程可执行线程不可能超过核心执行线程数,空余线程也将在超过等待时间之后被销毁。
  • 线程数达到最大线程数的时候,如果使用的是SynchronousQueue队列,则触发拒绝策略,反之,如果使用LinkedBlockingQueue队列,则会尝试将任务放入到队列中,如果队列满了,就需要执行拒绝策略。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表