newsinglethreadexecutor使用场景_Java线程池从使用到阅读源码(3/10)

本文介绍了线程池的基本使用方法,包括创建线程池、提交任务、关闭线程池及监控线程池运行状态。接着详细探讨了四种常用的线程池类型:可缓存线程池、定长线程池、延时任务线程池和单线程线程池,分析了它们的特点和适用场景。最后,通过对`ThreadPoolExecutor`的源码分析,揭示了线程池的内部实现和执行流程,帮助读者深入理解线程池的工作原理。
摘要由CSDN通过智能技术生成

071f8bd8f3cb4d90e52a0ad3488ad56d.png

我们一般不会选择直接使用线程类Thread进行多线程编程,而是使用更方便的线程池来进行任务的调度和管理。线程池就像共享单车,我们只要在我们有需要的时候去获取就可以了。甚至可以说线程池更棒,我们只需要把任务提交给它,它就会在合适的时候运行了。但是如果直接使用Thread类,我们就需要在每次执行任务时自己创建、运行、等待线程了,而且很难对线程进行整体的管理,这可不是一件轻松的事情。既然我们已经有了线程池,那还是把这些麻烦事交给线程池来处理吧。

这篇文章将会从线程池的概念与一般使用入手,首先让大家可以了解线程池的基本使用方法,之后会介绍实践中最常用的四种线程池。最后,我们会通过对JDK源代码的剖析深入了解线程池的运行过程和具体设计,真正达到知其然而知其所以然的水平。虽然只要了解了API就可以满足一般的日常使用了,但是只有当我们真正厘清了多线程相关的知识点,才能在面对多线程的实践与面试问题时做到游刃有余、成竹在胸。

本文是一系列多线程文章中的第三篇,主要讲解了线程池相关的知识,这个系列总共有十篇文章,前五篇暂定结构如下,感兴趣的读者可以关注一下:

1. 并发基本概念——当我们在说“并发、多线程”,说的是什么?

2. 多线程入门——这一次,让我们完全掌握Java多线程(2/10)

3. 线程池使用与原理剖析——本文

4. 线程同步机制

5. 并发常见问题

1 线程池的使用方法

一般我们最常用的线程池实现类是ThreadPoolExecutor,我们接下来会介绍这个类的基本使用方法。JDK已经对线程池做了比较好的封装,相信这个过程会非常轻松。

1.1 创建线程池

既然线程池是一个Java类,那么最直接的使用方法一定是new一个ThreadPoolExecutor类的对象,例如ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() )。那么这个构造器的里每个参数是什么意思呢?

下面就是这个构造器的方法签名:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)

各个参数分别表示下面的含义:

  1. corePoolSize,核心线程池大小,一般线程池会至少保持这么多的线程数量;
  2. maximumPoolSize,最大线程池大小,也就是线程池最大的线程数量;
  3. keepAliveTime和unit共同组成了一个超时间,keepAliveTime是时间数量,unit是时间单位,单位加数量组成了最终的超时时间。这个超时时间表示如果线程池中包含了超过corePoolSize数量的线程,则在有线程空闲的时间超过了超时时间时该线程就会被销毁;
  4. workQueue是任务的阻塞队列,在没有线程池中没有足够的线程可用的情况下会将任务先放入到这个阻塞队列中等待执行。这里传入的队列类型就决定了线程池在处理这些任务时的策略。

线程池中的阻塞队列专门用于存放待执行的任务,在ThreadPoolExecutor中一个任务可以通过两种方式被执行:第一种是直接在创建一个新的Worker时被作为第一个任务传入,由这个新创建的线程来执行;第二种就是把任务放入一个阻塞队列,等待线程池中的工作线程捞取任务进行执行。

上面提到的阻塞队列是这样的一种数据结构,它是一个队列(类似于一个List),可以存放0到N个元素。我们可以对这个队列进行插入和弹出元素的操作,弹出操作可以理解为是一个获取并从队列中删除一个元素的操作。当队列中没有元素时,对这个队列的获取操作将会被阻塞,直到有元素被插入时才会被唤醒;当队列已满时,对这个队列的插入操作将会被阻塞,直到有元素被弹出后才会被唤醒。这样的一种数据结构非常适合于线程池的场景,当一个工作线程没有任务可处理时就会进入阻塞状态,直到有新任务提交后才被唤醒。

1.2 提交任务

当创建了一个线程池之后我们就可以将任务提交到线程池中执行了。提交任务到线程池中相当简单,我们只要把原来传入Thread类构造器的Runnable对象传入线程池的execute方法或者submit方法就可以了。execute方法和submit方法基本没有区别,两者的区别只是submit方法会返回一个Future对象,用于检查异步任务的执行情况和获取执行结果(异步任务完成后)。

我们可以先试试如何使用比较简单的execute方法,代码例子如下:

public class ThreadPoolTest {
    

    private static int count = 0;

    public static void main(String[] args) throws Exception {
    
        Runnable task = new Runnable() {
    
            public void run() {
    
                for (int i = 0; i < 1000000; ++i) {
    
                    synchronized (ThreadPoolTest.class) {
    
                        count += 1;
                    }
                }
            }
        };

        // 重要:创建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 0L,
        TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

        // 重要:向线程池提交两个任务
        threadPool.execute(task);
        threadPool.execute(task);

        // 等待线程池中的所有任务完成
        threadPool.shutdown();
        while (!threadPool.awaitTermination(1L, TimeUnit.MINUTES)) {
    
            System.out.println("Not yet. Still waiting for termination");
        }

        System.out.println("count = " + count);
    }
}

1.3 关闭线程池

上面的代码中为了等待线程池中的所有任务执行完已经使用了shutdown()方法,关闭线程池的方法主要有两个:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值