• Java线程池


    创建线程的四种方式

    • 实现Runnable接口,重写run()方法(避免多继承局限)
    • 继承Thread类,重写run()方法(本质:Thread类也是实现Runnable接口)
    • 实现Callable接口,重写call()方法,有返回值
    • 使用线程池(使用原因:不推荐手动创建线程,不方便管理,易造成较大开销或浪费)

    初识线程池

    在Java中,我们可以利用多线程来最大化地压榨CPU多核计算的能力。但是,线程本身是把双刃剑,我们需要知道它的利弊,才能在实际系统中游刃有余地运用。

    线程池,本质上是一种对象池,用于管理线程资源。 在任务执行前,需要从线程池中拿出线程来执行。在任务执行完成之后,需要把线程放回线程池。通过线程的这种反复利用机制,可以有效地避免直接创建线程所带来的坏处。

    不使用线程池的坏处:

    1. 频繁的线程创建和销毁会占用更多的CPU和内存;
    2. 频繁的线程创建和销毁会对GC产生比较大的压力;
    3. 线程太多,线程切换带来的开销将不可忽视;
    4. 线程太少,多核CPU得不到充分利用,是一种浪费。

    线程池的好处:

    1. 降低资源的消耗。线程本身是一种资源,创建和销毁线程会有CPU开销;创建的线程也会占用一定的内存;
    2. 提高任务执行的响应速度。任务执行时,可以不必等到线程创建完之后再执行;
    3. 提高线程的可管理性。线程不能无限制地创建,需要进行统一的分配、调优和监控。

    因此,我们有必要对线程池进行比较完整地说明,以便能对线程池进行正确地治理。

    线程池实现原理

    线程池主要处理流程

    通过上图,我们看到了线程池的主要处理流程。我们的关注点在于,任务提交之后是怎么执行的。大致如下:

    1. 判断核心线程池是否已满,如果不是,则创建线程执行任务;
    2. 如果核心线程池满了,判断队列是否满了,如果队列没满,将任务放在队列中;
    3. 如果队列满了,则判断线程池是否已满,如果没满,创建线程执行任务;
    4. 如果线程池也满了,则按照拒绝策略对任务进行处理。

    在jdk里面,我们可以将处理流程描述得更清楚一点。来看看ThreadPoolExecutor的处理流程。

    ThreadPoolExecutor的处理流程:

    1. corePool -> 核心线程池
    2. maximumPool -> 线程池
    3. BlockQueue -> 队列
    4. RejectedExecutionHandler -> 拒绝策略

    代码示例

    Executors(不推荐使用)

    Executors类创建线程池的方法归根结底都是调用ThreadPoolExecutor类,只不过对每个方法赋值不同的参数去构造ThreadPoolExecutor对象。

    1. newCachedThreadPool:创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
    2. newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
    3. newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
    4. newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

    注意:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式创建。

    原因:上述四个方法的创建的队列大小默认都是Integer.MAX_VALUE,堆积过多的任务请求会可能导致OOM。

    public class ThreadPoolTest {
        public static void main(String[] args) {
            ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
            
            // 定时调度,每个调度任务会至少等待`period`的时间,
            // 如果任务执行的时间超过`period`,则等待的时间为任务执行的时间
            executor.scheduleAtFixedRate(() -> {
                try {
                    Thread.sleep(10000);
                    System.out.println(System.currentTimeMillis() / 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, 0, 2, TimeUnit.SECONDS);
            
            // 定时调度,延迟`delay`后执行,且只执行一次
            executor.schedule(() -> System.out.println("5 秒之后执行 schedule"), 5, TimeUnit.SECONDS);
        }
    }
    

    ThreadPoolExecutor

    • corePoolSize: 常驻核心线程数,如果大于0,即使本地任务执行完也不会被销毁
    • maximumPoolSize: 线程池能够容纳可同时执行的最大线程数
    • keepAliveTime: 线程池中线程空闲的时间,当空闲时间达到该值时,线程会被销毁, 只剩下 corePoolSize 个线程数量。
    • unit: 空闲时间的单位。一般以TimeUnit类定义时分秒。
    • workQueue: 当请求的线程数大于 corePoolSize 时,线程进入该阻塞队列。
      • LinkedBlockingQueue:无界队列,当不指定队列大小时,将会默认为Integer.MAX_VALUE大小的队列,因此大量的任务将会堆积在队列中,最终可能触发OOM。
      • ArrayBlockingQueue:有界队列,基于数组的先进先出队列,此队列创建时必须指定大小。
      • PriorityBlockingQueue:有界队列,基于优先级任务的,它是通过Comparator决定的。
      • SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务
    • threadFactory: 线程工厂,用来生产一组相同任务的线程,同时也可以通过它增加前缀名,虚拟机栈分析时更清晰
    • handler: 执行拒绝策略,当 workQueue 已满,且超过maximumPoolSize 最大值,就要通过这个来处理,比如拒绝,丢弃等,这是一种限流的保护措施。
      • AbortPolicy:默认的拒绝策略,抛RejectedExecutionException异常
      • DiscardPolicy:相当大胆的策略,直接丢弃任务,没有任何异常抛出
      • DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列
      • CallerRunsPolicy:提交任务的线程自己去执行该任务
    • 线程池关闭
      • shutdown() : 不会立刻终止线程,等所有缓存队列中的任务都执行完毕后才会终止。
      • shutdownNow() : 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
    • 线程池监控
      • long getTaskCount():获取已经执行或正在执行的任务数
      • long getCompletedTaskCount():获取已经执行的任务数
      • int getLargestPoolSize():获取线程池曾经创建过的最大线程数,根据这个参数,我们可以知道线程池是否满过
      • int getPoolSize():获取线程池线程数
      • int getActiveCount():获取活跃线程数(正在执行任务的线程数)
    public class ThreadPool {
        public static void main( String[] args ){
            // maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
            ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
            for(int i=0;i<3;i++) {
                pool.execute(new ThreadTask());
            }   
        }
    }
    
    class ThreadTask implements Runnable{
        
        public ThreadTask() {
        }
        
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    

    ThreadFactory(阿里Java开发手册推荐使用)

    // 使用工厂类可以设置线程名字
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
    ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
    

    注意需要引入guava包,否则ThreadFactoryBuilder会报错

    <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>28.1-jre</version>
    </dependency>
    
  • 相关阅读:
    通过百度地图API实现搜索地址--第三方开源--百度地图(三)
    对图片进行各种样式裁对图片进行各种样式裁剪:圆形、星形、心形、花瓣形等剪:圆形、星形、心形、花瓣形等--第三方开源--CustomShapeImageView
    仿UC天气下拉和微信下拉眼睛头部淡入淡出--第三方开源--PullLayout
    【英语】Bingo口语笔记(14)
    【前端】HTML入门笔记
    【英语】Bingo口语笔记(13)
    【英语】20141013 生词
    【英语】20141011 生词
    【Android】Android 学习记录贴
    【英语】Bingo口语笔记(12)
  • 原文地址:https://www.cnblogs.com/fengda1/p/15385918.html
Copyright © 2020-2023  润新知