JAVA线程池

应用中应避免显式直接创建线程示例去执行

线程池避免了大量创建与销毁线程所需要的成本,也可以避免峰值压力带来的瞬间大量线程创建带来的资源耗尽,程序崩溃的风险

JUC包对线程池的支持

在JDK的Java.util.concurrent包下Executors类利用工厂模式向我们提供了4种线程池实现方式

Java开发手册中的规约

阿里发布的 Java开发手册中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

ThreadPoolExecutor构造方法中需要的参数:

public ThreadPoolExecutor(//Executors类创建线程池也是底层使用这个类创建的
    int corePoolSize,//核心线程数
    int maximumPoolSize,//线程池最大线程数
    long keepAliveTime,//保持时间,当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
    TimeUnit unit,//是一个枚举,表示 keepAliveTime 的单位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7个可选值)。
    BlockingQueue<Runnable> workQueue,//存放任务的队列(存放需要被线程池执行的线程队列)。
    RejectedExecutionHandler handler;//拒绝策略(添加任务失败后如何处理该任务)
);

简单的线程池创建示例:

public class ThreadPoolTest implements Runnable {
    public void run() {
      synchronized(this) {
        try{
          System.out.println(Thread.currentThread().getName());
          Thread.sleep(3000);
        }catch (InterruptedException e){
          e.printStackTrace();
        }
      }
    }
 
   public static void main(String[] args) {
      BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
      ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 6, 1, TimeUnit.DAYS, queue);
      for (int i = 0; i < 10; i++) {
        executor.execute(new Thread(new ThreadPoolTest(),"TestThread".concat(""+i)));
        int threadSize = queue.size();
        System.out.println("线程队列大小为-->"+threadSize);
      }
      executor.shutdown();
    }
}

阻塞队列

JDK 7 提供了7个阻塞队列,如下

1、ArrayBlockingQueue 数组结构组成的有界阻塞队列。

此队列按照先进先出(FIFO)的原则对元素进行排序,但是默认情况下不保证线程公平的访问队列,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(即先阻塞,先插入)的。

2、LinkedBlockingQueue一个由链表结构组成的有界阻塞队列

此队列按照先出先进的原则对元素进行排序

3、PriorityBlockingQueue支持优先级的无界阻塞队列

4、DelayQueue支持延时获取元素的无界阻塞队列,即可以指定多久才能从队列中获取当前元素

5、SynchronousQueue不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素。并且他支持公平访问队列。

6、LinkedTransferQueue由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,多了tryTransfer和transfer方法

transfer方法:

如果当前有消费者正在等待接收元素(take或者待时间限制的poll方法),transfer可以把生产者传入的元素立刻传给消费者。如果没有消费者等待接收元素,则将元素放在队列的tail节点,并等到该元素被消费者消费了才返回。

tryTransfer方法:

用来试探生产者传入的元素能否直接传给消费者。,如果没有消费者在等待,则返回false。和上述方法的区别是该方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。

7、LinkedBlockingDeque链表结构的双向阻塞队列,优势在于多线程入队时,减少一半的竞争。

拒绝策略

拒绝策略也有四种:

ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出异常 (默认策略)。

ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:将最早进入队列的任务删,之后再尝试加入队列。

ThreadPoolExecutor.CallerRunsPolicy:如果添加到线程池失败,那么主线程会自己去执行该任务。

线程运行策略

1.线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。

2.当调用 execute() 方法添加一个任务时,线程池会做如下判断:

a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。

3.当一个线程完成任务时,它会从队列中取下一个任务来执行。

4.当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行 的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

五种线程池的区别及使用场景

1、newCachedThreadPool适合任务量大但耗时少的任务

new ThreadPoolExecutor(0, Integer.MAX_VALUE, 
60L, TimeUnit.SECONDS, 
new SynchronousQueue<Runnable>())

任务队列采用的是SynchronousQueue,这个队列是无法插入任务的,一有任务立即执行。

作用:创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。

特征:
(1)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE = 2n31-1)
(2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
(3)当线程池中,没有可用线程,会重新创建一个线程
(4)无核心线程,所有线程都是非核心线程

提交任务时,如果线程都处于活动状态则创建新线程,如果线程超过(keepAliveTime)60秒空闲会被回收,如果线程池空闲会回收所有线程

创建方式: Executors.newCachedThreadPool();

2、newFixedThreadPool —— 适用于任务量比较固定但耗时长的任务

new ThreadPoolExecutor(nThreads, nThreads, 
0L, TimeUnit.MILLISECONDS, 
new LinkedBlockingQueue<Runnable>())

任务队列采用了无界的阻塞队列LinkedBlockingQueue,执行execute方法的时候,运行的线程没有达到corePoolSize就创建核心线程执行任务,否则就阻塞在任务队列中,有空闲线程的时候去取任务执行。

作用:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

特征:
(1)线程池中的线程处于一定的量,可以很好的控制线程的并发量
(2)线程可以重复被使用,在显示关闭之前,都将一直存在
(3)超出一定量的线程被提交时候需在队列中等待
(4)全是核心线程,无非核心线程

无超时机制,无任务大小限制,数量固定,空闲不回收。

创建方式:
(1)Executors.newFixedThreadPool(int nThreads);//nThreads为线程的数量
(2)Executors.newFixedThreadPool(int nThreads,ThreadFactory threadFactory);//nThreads为线程的数量,threadFactory创建线程的工厂方式

3、newSingleThreadExecutor ——适用于多个任务顺序执行(当任务依赖上一个任务的结果时,就可以考虑 SingleThreadExecutor)的场景

new ThreadPoolExecutor(1, 1, 
0L, TimeUnit.MILLISECONDS, 
new LinkedBlockingQueue<Runnable>())

任务队列是LinkedBlockingQueue,这是个无界的阻塞队列,因为线程池里只有一个线程,就确保所有的任务都在同一个线程中顺序执行,这样就不需要处理线程同步的问题。

作用:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。

特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
(2)只拥有一个核心线程

可以单独用,也可以与周期线程池结合

创建方式:
(1)Executors.newSingleThreadExecutor() ;
(2)Executors.newSingleThreadExecutor(ThreadFactory threadFactory);// threadFactory创建线程的工厂方式

4、newScheduleThreadPool —— 这类线程池适用于执行定时任务和具体固定周期的重复任务

new ScheduledThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE, 
0, NANOSECONDS, 
new DelayedWorkQueue())

任务队列采用的DelayedWorkQueue是个无界的队列,延时执行队列任务。

作用: 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

特征:
(1)线程池中具有指定数量的线程,即便是空线程也将保留
(2)可定时或者延迟执行线程活动
(3)核心线程数固定,不回收;非核心线程无限制,空闲即回收

创建方式:
(1)Executors.newScheduledThreadPool(int corePoolSize);// corePoolSize线程的个数
(2)newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);// corePoolSize线程的个数,threadFactory创建线程的工厂

5、newSingleThreadScheduledExecutor
作用: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
(2)可定时或者延迟执行线程活动

创建方式:
(1)Executors.newSingleThreadScheduledExecutor() ;
(2)Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory) ;//threadFactory创建线程的工厂

开发中应根据实际场景使用ThreadPoolExecutor创建合适的线程池,例如:IO密集型任务,线程多数时间在等待IO完成,所有应创建更多的线程来充分利用CPU资源(2N)
CPU密集型任务,线程完成任务时间较短,创建少量线程可以避免线程切换带来的开销(N+1)

**N为CPU核心数

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hongmin.shm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值