线程池的7种创建方式

文章目录

    • 普通方式创建线程存在的问题
    • 什么是线程池
    • 线程池的好处
    • 线程池设计思路
    • 线程池相关类的继承关系
    • 线程池的创建方式
      • 固定容量线程池——FixedThreadPool
        • 相关构造方法
        • 示例
        • 运行结果
      • 缓存线程池——CachedThreadPool
        • 相关构造方法
        • 示例
        • 运行结果
      • 单线程线程池——SingleThreadExecutor
        • 相关构造方法
        • 示例
        • 运行结果
        • 结果分析
      • 定时线程池——ScheduledThreadPool
        • 相关构造方法
        • ScheduledThreadPoolExecutor类源码
        • 示例
        • 运行结果
        • 结果分析
      • 单线程定时线程池——SingleThreadScheduledExecutor
        • 相关构造方法
        • 示例
        • 运行结果
        • 结果分析
      • 抢占式线程池——WorkStealingPool【 JDK 1.8添加】
        • 相关构造方法
        • 示例
        • 运行结果
        • 结果分析
      • ThreadPoolExecutor的使用【推荐使用】
        • Executors方式创建线程池可能存在的问题
          • OOM 代码演示
          • 设置运行时环境变量
          • 参数设置解释
          • 运行结果
          • 结果分析
        • ThreadPoolExecutor的构造方法
          • 参数解释
          • 任务队列
          • 线程工厂
          • 拒绝策略
          • 测试代码
          • 运行结果
    • 线程池状态
      • 线程池有5种状态
      • 初始化&容量调整&关闭&任务提交
        • 线程初始化
        • 线程池关闭
        • 线程池容量调整
        • 向线程池提交任务
    • 常见面试题

普通方式创建线程存在的问题

  • 首先,每次有任务来时就要创建线程,任务结束就要将这个线程销毁,频繁的创建和销毁需要一定的开销。
  • 当任务数远远大于线程可以承载的数量之后,我们需要频繁的创建大量的线程,从而导致内存和CPU占比过高,服务器系统崩溃。我们可以使用线程池化的思想来人为根据服务器配置创建符合的线程数量。多余的任务进行友好的任务拒绝策略。

什么是线程池

线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可,其中核心线程默认+只要线程池不关闭,就不会被销毁,可以反复使用。

池化思想在计算机的应用也比较广泛,比如以下这些:

  • 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。
  • 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
  • 实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。

线程池的好处

  1. 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  2. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  4. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

线程池设计思路

有句话叫做艺术来源于生活,编程语言也是如此,很多设计思想能映射到日常生活中,比如面向对象思想、封装、继承,等等。今天我们要说的线程池,它同样可以在现实世界找到对应的实体——工厂。

先假想一个工厂的生产流程:

在这里插入图片描述

工厂中有固定的一批工人,称为正式工人,工厂接收的订单由这些工人去完成。当订单增加,正式工人已经忙不过来了,工厂会将生产原料暂时堆积在仓库中,等有空闲的工人时再处理(因为工人空闲了也不会主动处理仓库中的生产任务,所以需要调度员实时调度)。仓库堆积满了后,订单还在增加怎么办?工厂只能临时扩招一批工人来应对生产高峰,而这批工人高峰结束后是要清退的,所以称为临时工。当时临时工也以招满后(受限于工位限制,临时工数量有上限),后面的订单只能忍痛拒绝了。

我们做如下一番映射:

工厂——线程池
订单——任务(Runnable)
正式工人——核心线程
临时工——普通线程
仓库——任务队列
调度员——getTask()

getTask()是一个方法,将任务队列中的任务调度给空闲线程。

映射后,形成线程池流程图如下,两者是不是有异曲同工之妙?

在这里插入图片描述

这样,线程池的工作原理或者说流程就很好理解了,提炼成一个简图:

在这里插入图片描述

线程池相关类的继承关系

在这里插入图片描述

线程池的创建方式

线程池的创建⽅法总共有 7 种,但总体来说可分为 2 类:

  1. 通过 ThreadPoolExecutor 创建的线程池;
  2. 通过 Executors 创建的线程池。

线程池的创建⽅式总共包含以下 7 种(其中 6 种是通过 Executors 创建的, 1 种是通过 ThreadPoolExecutor 创建的):

  1. Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
  2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
  4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
  5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
  6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
  7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。

固定容量线程池——FixedThreadPool

固定容量线程池。其特点是最大线程数就是核心线程数,意味着线程池只能创建核心线程,keepAliveTime为0,即线程执行完任务立即回收。任务队列未指定容量,代表使用默认值Integer.MAX_VALUE。适用于需要控制并发线程的场景

相关构造方法

// 使用默认线程工厂
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

// 需要自定义线程工厂
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}

示例

package com.fastech.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestFixedThreadPool {

    public static void main(String[] args) {
        // 1. 创建线程池对象,设置核心线程和最大线程数为5
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        // 2. 创建Runnable(任务)
        Runnable task = new Runnable(){
            public void run() {
                System.out.println(Thread.currentThread().getName() + "--->运行");
            }
        };
        // 3. 向线程池提交任务
        fixedThreadPool.execute(task);
        fixedThreadPool.execute(task);
    }

}

运行结果

pool-1-thread-1—>运行
pool-1-thread-2—>运行

缓存线程池——CachedThreadPool

缓存线程池。没有核心线程,普通线程数量为Integer.MAX_VALUE(可以理解为无限),线程闲置60s后回收,任务队列使用SynchronousQueue这种无容量的同步队列。适用于任务量大但耗时低的场景。

相关构造方法

// 使用默认线程工厂
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

// 需要自定义线程工厂
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}

示例

package com.fastech.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestCachedThreadPool {

    public static void main(String[] args) {
        // 1. 创建缓存线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        // 2. 创建Runnable(任务)
        Runnable task = new Runnable(){
            public void run() {
                System.out.println(Thread.currentThread().getName() + "--->运行");
            }
        };
        // 3. 向线程池提交任务
        cachedThreadPool.execute(task);
        cachedThreadPool.execute(task);
    }

}

运行结果

pool-1-thread-1—>运行
pool-1-thread-2—>运行

单线程线程池——SingleThreadExecutor

单线程线程池。特点是线程池中只有一个线程(核心线程),线程执行完任务立即回收,使用有界阻塞队列(容量未指定,使用默认值Integer.MAX_VALUE

相关构造方法

// 使用默认线程工厂
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

// 需要自定义线程工厂
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}

示例

package com.fastech.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestSingleThreadExecutor {

    public static void main(String[] args) {
        // 1. 创建单线程线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        // 2. 创建Runnable(任务)
        Runnable task = new Runnable(){
            public void run() {
                System.out.println(Thread.currentThread().getName() + "--->运行");
            }
        };
        // 3. 向线程池提交任务
        singleThreadExecutor.execute(task);
        singleThreadExecutor.execute(task);
        singleThreadExecutor.execute(task);
        singleThreadExecutor.execute(task);
        singleThreadExecutor.execute(task);
        singleThreadExecutor.execute(task);
    }

}

运行结果

pool-1-thread-1—>运行
pool-1-thread-1—>运行
pool-1-thread-1—>运行
pool-1-thread-1—>运行
pool-1-thread-1—>运行
pool-1-thread-1—>运行

结果分析

可以看到运行结果中线程名称都是一样的,表示线程池中只有一个线程(核心线程)执行任务。

定时线程池——ScheduledThreadPool

定时线程池。指定核心线程数量,普通线程数量无限,普通线程执行完任务立即回收,任务队列为延时阻塞队列。这是一个比较特别的线程池,适用于执行定时或延迟的任务

相关构造方法

// 使用默认线程工厂
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

// 需要自定义线程工厂
public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

ScheduledThreadPoolExecutor类源码

// 继承了ThreadPoolExecutor
public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {
    
    // 使用默认线程工厂的构造方法
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        // 调用父类ThreadPoolExecutor的构造方法
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

    // 需要自定义线程工厂的构造方法
    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        // 调用父类ThreadPoolExecutor的构造方法
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        ...
    }

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        ...
    }
}

示例

package com.fastech.thread;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class TestScheduledThreadPool {

    public static void main(String[] args) {
        // 1. 创建定时线程池
        // ExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        // 注意此处不要使用ExecutorService类接收,因为schedule()和scheduleAtFixedRate()方法只是ScheduledExecutorService类的方法
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        // 2. 创建Runnable(任务)
        Runnable task = new Runnable(){
            public void run() {
                System.out.println(Thread.currentThread().getName() + "--->运行," + "任务被执行时间:" + System.currentTimeMillis());
            }
        };
        // 3. 向线程池提交任务
        System.out.println("添加任务时间:" + System.currentTimeMillis());
        scheduledExecutorService.schedule(task, 2, TimeUnit.SECONDS); // 延迟2s后执行任务
//        scheduledExecutorService.scheduleAtFixedRate(task,50,2000,TimeUnit.MILLISECONDS);// 延迟50ms后、每隔2000ms执行任务
    }

}

运行结果

添加任务时间:1680073402269
pool-1-thread-1—>运行,任务被执行时间:1680073404322

结果分析

可以看到运行结果中任务在两秒后被执行了,符合预期

单线程定时线程池——SingleThreadScheduledExecutor

单线程的可以执行定时或延迟任务的线程池,核心线程数量只有一个,普通线程数量无限,普通线程执行完任务立即回收,任务队列为延时阻塞队列。

相关构造方法

// 使用默认线程工厂
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
  return new DelegatedScheduledExecutorService
    (new ScheduledThreadPoolExecutor(1));
}

// 需要自定义线程工厂
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
  return new DelegatedScheduledExecutorService
    (new ScheduledThreadPoolExecutor(1, threadFactory));
}

示例

package com.fastech.thread;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class TestSingleThreadScheduledExecutor {

    public static void main(String[] args) {
        // 1. 创建单线程定时线程池
        // ExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        // 注意此处不要使用ExecutorService类接收,因为schedule()和scheduleAtFixedRate()方法只是ScheduledExecutorService类的方法
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        // 2. 创建Runnable(任务)
        Runnable task = new Runnable(){
            public void run() {
                System.out.println(Thread.currentThread().getName() + "--->运行," + "任务被执行时间:" + System.currentTimeMillis());
            }
        };
        // 3. 向线程池提交任务
        System.out.println("添加任务时间:" + System.currentTimeMillis());
        scheduledExecutorService.schedule(task, 2, TimeUnit.SECONDS); // 延迟2s后执行任务
        scheduledExecutorService.schedule(task, 2, TimeUnit.SECONDS); // 延迟2s后执行任务
        scheduledExecutorService.schedule(task, 2, TimeUnit.SECONDS); // 延迟2s后执行任务
        scheduledExecutorService.schedule(task, 2, TimeUnit.SECONDS); // 延迟2s后执行任务
        scheduledExecutorService.schedule(task, 2, TimeUnit.SECONDS); // 延迟2s后执行任务
        scheduledExecutorService.schedule(task, 2, TimeUnit.SECONDS); // 延迟2s后执行任务
//        scheduledExecutorService.scheduleAtFixedRate(task,50,2000,TimeUnit.MILLISECONDS);// 延迟50ms后、每隔2000ms执行任务
    }

}

运行结果

添加任务时间:1680074041101
pool-1-thread-1—>运行,任务被执行时间:1680074043160
pool-1-thread-1—>运行,任务被执行时间:1680074043160
pool-1-thread-1—>运行,任务被执行时间:1680074043160
pool-1-thread-1—>运行,任务被执行时间:1680074043161
pool-1-thread-1—>运行,任务被执行时间:1680074043161
pool-1-thread-1—>运行,任务被执行时间:1680074043161

结果分析

可以看到运行结果中线程名称都是一样的,表示线程池中只有一个线程(核心线程)执行任务,而且任务在两秒后被执行了,符合预期

抢占式线程池——WorkStealingPool【 JDK 1.8添加】

抢占式线程池,newWorkStealingPool使用的是ForkJoinPool任务拆分合并线程池,**若指定并发数,线程数量就为并发数WorkStealingPool能够做到并行执行与设置的并发数相同的任务数量;若不指定并发数,线程数量固定为CPU处理器个数,最大限度利用CPU进行密集运算。每个线程都有一个任务队列存放任务,当自己队列的任务执行完会窃取其他线程的队列任务,因此又叫抢占式线程池,故不能保证任务的执行顺序 **。适用于任务密集情况,每个任务执行时间长短均可。注意此方法只有在 JDK 1.8 及1.8+ 版本中才能使用。

相关构造方法

// 指定并发数为线程数量
public static ExecutorService newWorkStealingPool(int parallelism) {
  return new ForkJoinPool
    (parallelism,
     ForkJoinPool.defaultForkJoinWorkerThreadFactory,
     null, true);
}

// 不指定并发数,线程数量固定为CPU处理器个数
public static ExecutorService newWorkStealingPool() {
  return new ForkJoinPool
    (Runtime.getRuntime().availableProcessors(),
     ForkJoinPool.defaultForkJoinWorkerThreadFactory,
     null, true);
}

示例

package com.fastech.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class TestWorkStealingPool {

    // AtomicInteger用来计数
    static AtomicInteger number = new AtomicInteger();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newWorkStealingPool(2);
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("第" + number.incrementAndGet() + "周期线程运行当前时间【" + System.currentTimeMillis() + "】");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        // 避免其它线程任务还没运行完成,但主线程已经运行结束
        TimeUnit.SECONDS.sleep(5);
        System.out.println("主线程运行当前时间【" + System.currentTimeMillis() + "】");
    }

}

运行结果

第2周期线程运行当前时间【1680077725927】
第1周期线程运行当前时间【1680077725927】
第3周期线程运行当前时间【1680077726935】
第4周期线程运行当前时间【1680077726935】
第6周期线程运行当前时间【1680077727943】
第5周期线程运行当前时间【1680077727943】
第7周期线程运行当前时间【1680077728956】
第8周期线程运行当前时间【1680077728956】
第9周期线程运行当前时间【1680077729967】
第10周期线程运行当前时间【1680077729967】
主线程运行当前时间【1680077730935】

结果分析

可以看到是几个任务是并行执行且无序的。而且每两个线程的执行时间是相同的。这是因为并发数只设置了2个,那么第三个任务需要被执行就得等待线程资源了。

ThreadPoolExecutor的使用【推荐使用】

通过上面6种线程池的创建方式,以及每种线程池的构造方法,可以很清楚的知道,除了WorkStealingPool线程池,其它线程池的创建都是直接或者间接的创建ThreadPoolExecutor线程池,相当于对ThreadPoolExecutor线程池做的一些封装,用来提供给不同的场景使用!

Executors方式创建线程池可能存在的问题

根据阿里巴巴Java开发手册规定:

在这里插入图片描述

OOM 代码演示
package com.fastech.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestOOM {

    static class MyOOMClass {
        // 1M 空间(M KB Byte)
        private byte[] bytes = new byte[1 * 1024 * 1024];
    }

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(15 * 1000);
        ExecutorService service = Executors.newCachedThreadPool();
        Object[] objects = new Object[15];
        for (int i = 0; i < 15; i++) {
            int finalI = i;
            service.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep( 200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    MyOOMClass myOOMClass = new MyOOMClass();
                    objects[finalI] = myOOMClass;
                    System.out.println("任务:" + finalI);
                }
            });
        }
    }

}
设置运行时环境变量

在这里插入图片描述

参数设置解释

-XX:标准设置,所有 HotSpot 都⽀持的参数。
-X:⾮标准设置,特定的 HotSpot 才⽀持的参数。
-D:程序参数设置,-D参数=value,程序中使⽤:System.getProperty(“获取”)。

mx 是 memory max 的简称

运行结果

在这里插入图片描述

结果分析

演示代码是因为创建大数组,导致堆内存(Heap Space)没有足够空间存放创建的数组,就会抛出 java.lang.OutOfMemoryError:Javaheap space 错误。这就是常说的jvm堆内存溢出的案例。

原因分析

Javaheap space 错误产生的常见原因可以分为以下几类:
1、请求创建一个超大对象,通常是一个大数组。
2、超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。
3、过度使用终结器(Finalizer),该对象没有立即被 GC。
4、内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收。

ThreadPoolExecutor的构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
参数解释
  • corePoolSize(必需):核心线程数。即池中一直保持存活的线程数,即使这些线程处于空闲。但是将allowCoreThreadTimeOut参数设置为true后,核心线程处于空闲一段时间以上,也会被回收。
  • maximumPoolSize(必需):池中允许的最大线程数。当核心线程全部繁忙且任务队列打满之后,线程池会临时追加线程,直到总线程数达到maximumPoolSize这个上限。
  • keepAliveTime(必需):线程空闲超时时间。当非核心线程处于空闲状态的时间超过这个时间后,该线程将被回收。将allowCoreThreadTimeOut参数设置为true后,核心线程也会被回收。
  • unit(必需):keepAliveTime参数的时间单位。有:
    • TimeUnit.DAYS(天)
    • TimeUnit.HOURS(小时)
    • TimeUnit.MINUTES(分钟)
    • TimeUnit.SECONDS(秒)
    • TimeUnit.MILLISECONDS(毫秒)
    • TimeUnit.MICROSECONDS(微秒)
    • TimeUnit.NANOSECONDS(纳秒)
  • workQueue(必需):任务队列,采用阻塞队列实现。当核心线程全部繁忙时,后续由execute方法提交的Runnable将存放在任务队列中,等待被线程处理。
  • threadFactory(可选):线程工厂。指定线程池创建线程的方式。
  • handler(可选):拒绝策略。当线程池中线程数达到maximumPoolSize且workQueue打满时,后续提交的任务将被拒绝,handler可以指定用什么方式拒绝任务。
任务队列

使用ThreadPoolExecutor需要指定一个实现了BlockingQueue接口的任务等待队列。在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueue、LinkedBlockingQueue和ArrayBlockingQueue。

  1. SynchronousQueue同步队列。这是一个内部没有任何容量的阻塞队列,任何一次插入操作的元素都要等待相对的删除/读取操作,否则进行插入操作的线程就要一直等待,反之亦然。
  2. LinkedBlockingQueue无界队列(严格来说并非无界,上限是Integer.MAX_VALUE),基于链表结构。使用无界队列后,当核心线程都繁忙时,后续任务可以无限加入队列,因此线程池中线程数不会超过核心线程数。这种队列可以提高线程池吞吐量,但代价是牺牲内存空间,甚至会导致内存溢出。另外,使用它时可以指定容量,这样它也就是一种有界队列了。
  3. ArrayBlockingQueue有界队列,基于数组实现。在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制。

另外,Java还提供了另外4种队列:

  1. PriorityBlockingQueue支持优先级排序的无界阻塞队列。存放在PriorityBlockingQueue中的元素必须实现Comparable接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。
  2. DelayQueue延迟队列。基于二叉堆实现,同时具备:无界队列、阻塞队列、优先队列的特征。DelayQueue延迟队列中存放的对象,必须是实现Delayed接口的类对象。通过执行时延从队列中提取任务,时间没到任务取不出来。更多内容请见DelayQueue。
  3. LinkedBlockingDeque双端队列。基于链表实现,既可以从尾部插入/取出元素,还可以从头部插入元素/取出元素。
  4. LinkedTransferQueue由链表结构组成的无界阻塞队列。这个队列比较特别的时,采用一种预占模式,意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素。
线程工厂

线程工厂指定创建线程的方式,这个参数不是必选项,Executors类已经为我们非常贴心地提供了一个默认的线程工厂,如果不指定线程工厂则使用默认的线程工厂。

/**
 * The default thread factory
 */
static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

当然我们也可以自定义工厂,实现ThreadFactory类,重写newThread方法即可。

package com.fastech.thread;

import java.util.concurrent.ThreadFactory;

public class SimpleThreadFactory implements ThreadFactory {

    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r);
    }

}

上述线程工厂,只是创建了一个新线程,其他什么都没干。实际使用时,一般不会创建这么简单的线程工厂。

自定义一个能指定最大线程数上限的工厂。

package com.fastech.thread;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassName: FixCountThreadFactory
 * @Description:
 * @Author: zhangjin
 * @Date: 2023/3/29
 */
public class FixCountThreadFactory implements ThreadFactory {
    
    private final int MAX_THREAD;

    private final AtomicInteger count = new AtomicInteger(0);

    public FixCountThreadFactory(int maxThread) {
        MAX_THREAD = maxThread;
    }

    @Override
    public Thread newThread(Runnable r) {
        int incrementAndGet = count.incrementAndGet();
        if(incrementAndGet > MAX_THREAD)
        {
            count.decrementAndGet();
            return null;
        }

        return new Thread(r);
    }
    
}

拒绝策略

线程池有一个重要的机制:拒绝策略。当线程池workQueue已满且无法再创建新线程池时,就要拒绝后续任务了。拒绝策略需要实现RejectedExecutionHandler接口,不过Executors框架已经为我们实现了4种拒绝策略。除此之外,也可以自定义拒绝策略。

  1. AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。

  2. CallerRunsPolicy:直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理。

  3. DiscardPolicy:直接丢弃任务,不抛出任何异常。

  4. DiscardOldestPolicy:将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。

  5. 自定义拒绝策略

    package com.fastech.thread;
    
    import java.util.concurrent.*;
    
    public class TestRejectedExecutionHandler {
    
        public static void main(String[] args) {
    
            // 自定义拒绝策略
            ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 10,
                    TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new RejectedExecutionHandler() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    System.out.println("我执行了自定义拒绝策略");
                }
            });
            for (int i = 0; i < 5; i++) {
                executor.submit(()->{
                    System.out.println("任务名:"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
    
            // 关闭线程池
            executor.shutdown();
        }
    
    }
    

    运行结果

    我执行了自定义拒绝策略
    任务名:pool-1-thread-2
    任务名:pool-1-thread-1
    任务名:pool-1-thread-1
    任务名:pool-1-thread-2

    结果分析

    定义了一个核心线程和最大线程都为2(即只有2个核心线程)、线程空闲超时时间10s,任务队列采用双端队列,长度为2,线程工厂默认,自定义拒绝策略的线程池,提交了五个任务,可以看到有一个任务被拒绝了,打印在最前面因为5个任务同时被加入线程池有两个任务被核心线程执行,两个任务放到了任务队列,还有一个任务被拒绝。拒绝的这个任务肯定是最快执行到的。

测试代码
package com.fastech.thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThreadPoolExecutor {

    public static void main(String[] args) {
        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3,
                                                            5,
                                                            5,
                                                            TimeUnit.SECONDS,
                                                            new ArrayBlockingQueue<Runnable>(5));
        // 向线程池提交任务
        for (int i = 0; i < executor.getCorePoolSize(); i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 2; j++) {
                        System.out.println(Thread.currentThread().getName() + ":" + j);
                    }
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        // 关闭线程
        executor.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
        // executor.shutdownNow(); // 设置线程池的状态为STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,该方法要慎用,容易造成不可控的后果
    }

}
运行结果

pool-1-thread-1:0
pool-1-thread-3:0
pool-1-thread-2:0
pool-1-thread-2:1
pool-1-thread-3:1
pool-1-thread-1:1

线程池状态

线程池有5种状态

volatile int runState;
// runState is stored in the high-order bits
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;

runState表示当前线程池的状态,它是一个 volatile 变量用来保证线程之间的可见性。

下面的几个static final变量表示runState可能的几个取值,有以下几个状态:

线程池各个状态切换框架图:

在这里插入图片描述

1、RUNNING

(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(2) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

2、 SHUTDOWN

(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务,它会等待所有任务执行完毕。
(2) 状态切换:调用线程池的shutdown()方法时,线程池由RUNNING -> SHUTDOWN。

3、STOP

(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4、TIDYING

(1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5、 TERMINATED

(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

初始化&容量调整&关闭&任务提交

线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

prestartCoreThread():初始化一个核心线程
prestartAllCoreThreads():初始化所有核心线程,并返回初始化的线程数

public boolean prestartCoreThread() {
    return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}

public int prestartAllCoreThreads() {
    int n = 0;
    while (addIfUnderCorePoolSize(null)) //注意传进去的参数是null
        ++n;
    return n;
}

线程池关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭:

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

线程池容量调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:

setCorePoolSize():设置核心池大小
setMaximumPoolSize():设置线程池最大能创建的线程数目大小

当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

向线程池提交任务

向线程池提交任务可以使用两个方法,分别为 execute() 和 submit()。

execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。execute() 方法中传入的是 Runnable 类的实例。
submit() 方法用于提交需要返回值的任务线程池会返回一个 Future 类型的对象,通过 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get() 方法来获取返回值。get() 方法会阻塞当前线程直到任务完成,使用 get(long timeout, TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,这时候可能任务没有执行完。

常见面试题

Q:假设我们有一个线程池,核心线程数为10,最大线程数也为20,任务队列为100。现在来了100个任务,线程池里现在有几个线程运行?

A:平常人可能就直接回答10个,但是基本这样回答就回去等通知了~这道题正确的答案是"不一定!",因为并没指明是哪一种线程池机制。如果是ThreadPoolExecutor线程池,没什么问题就是10个。但是,有一种线程池机制的是这样的,这一种线程池叫EagerThreadPoolExecutor线程池。EagerThreadPoolExecutor线程池通过自定义队列的这么一种形式,改写了线程池的机制。这种线程池的机制是核心线程数不够了,先起线程,当线程达到最大值后,后面的任务就丢进队列!


Q:线程只能在任务到达时才启动吗?

A:默认情况下,即使是核心线程也只能在新任务到达时才创建和启动。但是我们可以使用 prestartCoreThread(启动一个核心线程)或 prestartAllCoreThreads(启动全部核心线程)方法来提前启动核心线程。


Q:线程池创建的线程会一直运行吗?

A:可以通过keepAliveTime参数来控制线程是否一直运行,一般来说当线程数超过核心线程数之后如果多出来的线程没有任务执行那么在等待keepAliveTime时间之后就会被销毁。核心线程一般不受keepAliveTime参数影响,也就是说它会一直存在,因为allowCoreThreadTimeOut默认为false,但是如果将allowCoreThreadTimeOut参数为true,核心线程也会受KeepAliveTime参数的影响。需要注意的是如果你想将allowCoreThreadTimeOut参数设置为true,那么keepAliveTime参数必须大于0。否则会抛出非法参数异常。


Q:线程池中核心线程数量大小怎么设置?

A:「CPU密集型任务」:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。尽量使用较小的线程池,一般为CPU核心数+1。因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

「IO密集型任务」:比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。可以使用稍大的线程池,一般为2*CPU核心数。IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

另外:线程的平均工作时间所占比例越高,就需要越少的线程;线程的平均等待时间所占比例越高,就需要越多的线程;

以上只是理论值,实际项目中建议在本地或者测试环境进行多次调优,找到相对理想的值大小。


Q:说说submit(和 execute两个方法有什么区别?

A:submit() 和 execute() 都是用来执行线程池的,只不过使用 execute() 执行线程池不能有返回值,而使用 submit() 可以使用 Future 接收线程池执行的返回值。


Q:线程池为什么需要使用(阻塞)队列,不使用非阻塞队列?

A:阻塞队列主要是用于生产者-消费者模型的情况。举例一种情况,当核心线程达到最大时,此时新建的任务会加到任务队列里面去,后续,核心线程有线程空闲时,就会从任务队列里面取出任务执行。后续当任务队列中任务数为空时。如果使用的时阻塞队列,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒(不需要我们编写代码去唤醒)。这样提供了极大的方便性。如果使用非阻塞队列,它不会对当前线程产生阻塞,就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就非常麻烦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/6621.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于国产化系统银河麒麟(Kylin)的问题记录--持续更新

kylin 镜像 &#xff1a; Kylin-Server-10-SP2-x86-Release-Build09-20210524 Kylin-Server-10-SP1-Release-Build20-20210518-x86_64 1.ansible 模块无法使用yum 报错&#xff1a;"msg": "The Python 2 bindings for rpm are needed for this module. If you r…

Dart语言操作符?和!的用法

一.基本使用 1. ? 操作符跟在类型后面&#xff0c;表示当前变量可为null。 int a null; //这句代码在有空安全时&#xff0c;编译会提示错误如果想给一个变量赋值null要如何处理呢&#xff1f;只需要在类型 后面添加操作符&#xff1f;即可&#xff0c;eg: int? a null…

UWB高精度定位系统源码,工业安全定位系统源码

基于VueSpring boot前后端分离架构开发的一套UWB高精度定位系统源码。有演示。 文末获取联系 系统采用UWB高精度定位技术&#xff0c;可实现厘米级别定位。UWB作为一种高速率、低功耗、高容量的新兴无线局域定位技术&#xff0c;目前应用主要聚焦在室内外精确定位。在工业自动化…

spring boot 实现根据用户名查找用户功能

目录 1、UserEnetity类 2、UserMapper类 3、UserService类 4、UserController类 5、postman测试结果 为了实现根据用户名查询用户功能&#xff0c;我们需要在spring boot框架当中编写一下几个类&#xff1a; 1、UserEnetity类 它是根据数据库表的实体类&#xff0c;用于…

Downie 4 4.6.13 MAC上最好的一款视频下载工具

Downie for Mac 简介 Downie是Mac下一个简单的下载管理器&#xff0c;可以让您快速将不同的视频网站上的视频下载并保存到电脑磁盘里然后使用您的默认媒体播放器观看它们。 Downie 4 Downie 4 for Mac Downie 4 for Mac软件特点 支持许多站点 -当前支持1000多个不同的站点&…

蓝桥杯第19天(Python)(疯狂刷题第2天)

题型&#xff1a; 1.思维题/杂题&#xff1a;数学公式&#xff0c;分析题意&#xff0c;找规律 2.BFS/DFS&#xff1a;广搜&#xff08;递归实现&#xff09;&#xff0c;深搜&#xff08;deque实现&#xff09; 3.简单数论&#xff1a;模&#xff0c;素数&#xff08;只需要…

国产ARM+FPGA架构在“能源电力”中的典型应用详解

能源电力作为国民经济发展的“先导产业”和“基础行业”,面对当今复杂多变的国际形势,国内能源电力企业为追求更高的自主可控,正不断寻求各种经过行业验证的国产方案。 而单ARM架构已很难应对能源电力多通道/高速AD数据采集、处理、存储和显示的应用场景。目前,ARM + FPGA异…

Linux系统-gunzip命令简介以及常用参数

命令 – 解压提取文件内容 gzip命令 gzip命令是一种数据压缩方式&#xff0c;它是在Linux操作系统中常用的一种压缩工具&#xff0c;是GNU项目中自带的压缩程序之一。它是采用Lempel-Ziv编码(LZ77)和哈夫曼编码(Huffman Coding)进行压缩数据的&#xff0c;被广泛应用于软件发…

用Qt编写STM32烧录软件(ISP模式)代码

1.前言 之前写了一篇【用Qt编写STM32烧录软件&#xff08;ISP模式&#xff09;】&#xff0c;但是在文中没有具体的实现代码。 现在补上&#xff0c;各位有兴趣的同学可以参考以下。但是代码里面还有很多没有完善的&#xff0c;必定会存在一些bug&#xff0c;目前只是堪堪能用…

redis-数据结构、io

1.redis 数据结构 1.1 字典 kv是什么 每个键值对都会是一个dictEntry set hello word为例&#xff0c;因为Redis是KV键值对的数据库&#xff0c;每个键值对都会有一个dictEntry(源码位置&#xff1a;dict.h) 简单描述 加载原理 server 启动&#xff0c;加载redisdb进内存形…

厉害了!Facebook优惠广告让你的广告预算翻倍

如果你是一个企业主或者市场营销人员&#xff0c;你可能已经知道Facebook广告平台是一个强大的数字广告工具&#xff0c;可以帮助你推广产品和服务&#xff0c;并且吸引潜在客户到你的网站上。 而今天&#xff0c;我们将教你如何创建和投放Facebook优惠广告以及如何利用这些广…

【Redis学习】Redis事务

理论简介 是什么 可以一次执行多个命令&#xff0c;本质是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序地串行化执行而不会被其它命令插入&#xff0c;不许加塞。 能干嘛 一个队列中&#xff0c;一次性、顺序性、排他性的执行一系列命令 Redis事务 V…

花3个月面过华为测开岗,拿个30K不过分吧?真不是凡尔赛。。。

背景介绍 美本计算机专业&#xff0c;代码能力一般&#xff0c;之前有过两段实习以及一个学校项目经历。第一份实习是大二暑期在深圳的一家互联网公司做前端开发&#xff0c;第二份实习由于大三暑假回国的时间比较短&#xff08;小于两个月&#xff09;&#xff0c;于是找的实…

Apsara Clouder阿里巴巴开发规范认证

apsara n. 飞天&#xff1b;阿普萨拉 俗话说&#xff1a;“没有规矩&#xff0c;不成方圆。”今天我们来介绍一下&#xff0c;阿里巴巴对于开发规范的考试认证。 报名地址&#xff1a; https://edu.aliyun.com/certification/cldt04 1.证书 我们先来看一下考试通过的证书&…

vue 实现左滑图片验证

前言 众所周知&#xff0c;网页中滑动图片验证一直是各大网站、移动端的主流校验方式&#xff0c;其主要作用是为了区分人和机器以及为了防止机器人程序暴力登录或攻击从而设置的一种安全保护方式。从开发的角度来看要想实现这个功能还是需要一点时间的&#xff0c;但其实网上已…

C++源码剖析——forward_list

前言&#xff1a;之前看过侯老师的《STL源码剖析》但是那已经是多年以前的&#xff0c;现在工作中有时候查问题和崩溃都需要了解实际工作中使用到的STL的实现。因此计划把STL的源码再过一遍。   摘要&#xff1a;本文描述了llvm中libcxx的forward_list的实现。   关键字&am…

【排序算法】排序算法介绍及插入排序 ( 直接插入排序 希尔排序 )

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录1.排序的概念和运用1…

关于Warning: World-writable config file ‘/etc/mysql/my.cnf‘ is ignored

不知道那个大兄弟&#xff0c;更改了my.cnf的权限为 0777 登陆mysqll的时候提示&#xff1a;Warning: World-writable config file /etc/mysql/my.cnf is ignored 里面的配置被忽略了, my.cnf不起作用 如果不是安装在docker里面的话&#xff0c;直接 chmod 0644 /etc/mysql/…

Java每日一练(20230405)

目录 1. 地下城游戏 &#x1f31f;&#x1f31f;&#x1f31f; 2. 汇总区间 &#x1f31f;&#x1f31f; 3. 寻找旋转排序数组中的最小值 II &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C…

Selenium被检测为爬虫,怎么屏蔽和绕过

01、Selenium 操作被屏蔽 使用selenium自动化网页时&#xff0c;有一定的概率会被目标网站识别&#xff0c;一旦被检测到&#xff0c;目标网站会拦截该客户端做出的网页操作。 比如淘宝和大众点评的登录页&#xff0c;当手工打开浏览器&#xff0c;输入用户名和密码时&#x…