• [技术干货] Volatile不保证原子性及解决方案
    原子性的意义原子性特别是在并发编程领域,是一个极其重要的概念,原子性指的是一个操作或一组操作要么全部执行成功,要么全部不执行,不会出现部分执行的情况。这意味着原子性操作是不可分割的,它们在执行过程中不会被其他操作中断或干扰。原子性的意义在于它保证了数据的一致性和程序的正确性。在多线程或多进程的环境中,当多个操作同时访问和修改共享数据时,如果没有原子性保证,可能会导致数据不一致或不确定的结果。例如,如果一个线程在读取某个数据时,另一个线程同时修改了这个数据,那么第一个线程读取到的数据可能是不正确的。通过确保操作的原子性,可以避免这种情况,从而维护数据的完整性和程序的正确执行。了解了上面的原子性的重要概念后,接下来一起聊一聊 volatile 关键字。volatile 关键字在 Java 中用于确保变量的更新对所有线程都是可见的,但它并不保证复合操作的原子性。这意味着当多个线程同时访问一个 volatile 变量时,可能会遇到读取不一致的问题,尽管它们不会看到部分更新的值。Volatile 的限制不保证原子性:volatile 变量的单个读写操作是原子的,但复合操作(如自增或同步块)不是原子的。不保证顺序性:volatile 变量的读写操作不会与其他操作(如非 volatile 变量的读写)发生重排序。一个例子用一个示例来解释会更清楚点,假如我们有一段代码是这样的:class Counter { private volatile int count = 0; void increment() { count++; } int getCount() { return count; } } 尽管 count 是 volatile 变量,但 increment 方法中的复合操作 count++(读取-增加-写入)不是原子的。因此,在多线程环境中,多个线程可能会同时读取相同的初始值,然后增加它,导致最终值低于预期。volatile 不保证原子性的代码验证以下是一个简单的 Java 程序,演示了 volatile 变量在多线程环境中不保证复合操作原子性的问题:public class VolatileTest { private static volatile int counter = 0; public static void main(String[] args) throws InterruptedException { int numberOfThreads = 10000; Thread[] threads = new Thread[numberOfThreads]; for (int i = 0; i < numberOfThreads; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 100; j++) { counter++; } }); threads[i].start(); } for (int i = 0; i < numberOfThreads; i++) { threads[i].join(); } System.out.println("Expected count: " + (numberOfThreads * 100)); System.out.println("Actual count: " + counter); } }在这个例子中:counter 是一个 volatile 变量。每个线程都会对 counter 执行 100 次自增操作。理论上,如果 counter++ 是原子的,最终的 counter 值应该是 10000 * 100。然而,由于 counter++ 包含三个操作:读取 counter 的值、增加 1、写回 counter 的值,这些操作不是原子的。因此,在多线程环境中,最终的 counter 值通常会小于预期值,这证明了 volatile 变量不保证复合操作的原子性。解决方案1. 使用 synchronized 方法或块:将访问 volatile 变量的方法或代码块声明为 synchronized,确保原子性和可见性。class Counter { private volatile int count = 0; synchronized void increment() { count++; } synchronized int getCount() { return count; } }2. 使用 AtomicInteger 类:java.util.concurrent.atomic 包中的 AtomicInteger 提供了原子操作,可以替代 volatile 变量。import java.util.concurrent.atomic.AtomicInteger; class Counter { private AtomicInteger count = new AtomicInteger(0); void increment() { count.incrementAndGet(); } int getCount() { return count.get(); } }3. 使用锁(如 ReentrantLock):使用显式锁(如 ReentrantLock)来同步访问 volatile 变量的代码块。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Counter { private volatile int count = 0; private final Lock lock = new ReentrantLock(); void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }使用volatile变量的正确使用场景如果操作是简单的读写,并且你只需要保证可见性,可以使用 volatile。但对于复合操作,可以使用上述其他方法来实现,通过这些方法,可以确保在多线程环境中对共享资源的正确同步和可见性。转载自https://www.cnblogs.com/wgjava/p/18311697
  • [技术干货] Java Executors类的9种创建线程池的方法及应用场景分析
    在Java中,Executors 类提供了多种静态工厂方法来创建不同类型的线程池。在学习线程池的过程中,一定避不开Executors类,掌握这个类的使用、原理、使用场景,对于实际项目开发时,运用自如,以下是一些常用的方法,V哥来一一细说:newCachedThreadPool(): 创建一个可缓存的线程池,如果线程池中的线程超过60秒没有被使用,它们将被终止并从缓存中移除。newFixedThreadPool(int nThreads): 创建一个固定大小的线程池,其中 nThreads 指定了线程池中线程的数量。newSingleThreadExecutor(): 创建一个单线程的执行器,它创建单个工作线程来执行任务。newScheduledThreadPool(int corePoolSize): 创建一个固定大小的线程池,它可以根据需要创建新线程,但会按照固定延迟执行具有给定初始延迟的任务。newWorkStealingPool(int parallelism): 创建一个工作窃取线程池,它使用多个队列,每个线程都从自己的队列中窃取任务。newSingleThreadScheduledExecutor(): 创建一个单线程的调度执行器,它可以根据需要创建新线程来执行任务。privilegedThreadFactory(): 创建一个线程工厂,用于创建具有特权访问的线程。defaultThreadFactory(): 创建一个默认的线程工厂,用于创建具有非特权访问的线程。unconfigurableExecutorService(ExecutorService executor): 将给定的 ExecutorService 转换为不可配置的版本,这样调用者就不能修改它的配置。这些方法提供了灵活的方式来创建和管理线程池,以满足不同的并发需求,下面 V 哥来一一介绍一下9个方法的实现以及使用场景。1. newCachedThreadPool()newCachedThreadPool 方法是 Java java.util.concurrent 包中的 Executors 类的一个静态工厂方法。这个方法用于创建一个可缓存的线程池,它能够根据需要创建新线程,并且当线程空闲超过一定时间后,线程会被终止并从线程池中移除。下面是 newCachedThreadPool 方法的大致实现原理和源代码分析:实现原理线程创建: 当提交任务到线程池时,如果线程池中的线程数少于核心线程数,会创建新的线程来执行任务。线程复用: 如果线程池中的线程数已经达到核心线程数,新提交的任务会被放入任务队列中等待执行。线程回收: 如果线程池中的线程在一定时间内(默认是60秒)没有任务执行,它们会被终止,从而减少资源消耗。源代码分析在 Java 的 java.util.concurrent 包中,Executors 类并没有直接提供 newCachedThreadPool 的实现,而是通过调用 ThreadPoolExecutor 类的构造函数来实现的。以下是 ThreadPoolExecutor 构造函数的调用示例:public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }参数解释:corePoolSize: 核心线程数,这里设置为0,表示线程池不会保留任何核心线程。maximumPoolSize: 最大线程数,这里设置为 Integer.MAX_VALUE,表示理论上可以创建无限多的线程。keepAliveTime: 当线程数大于核心线程数时,多余的空闲线程能等待新任务的最长时间,这里设置为60秒。unit: keepAliveTime 参数的时间单位,这里是秒。workQueue: 一个任务队列,这里使用的是 SynchronousQueue,它是一个不存储元素的阻塞队列,每个插入操作必须等待一个相应的移除操作。实现过程初始化: 当调用 newCachedThreadPool 时,会创建一个 ThreadPoolExecutor 实例。任务提交: 当任务提交给线程池时,线程池会检查是否有空闲线程可以立即执行任务。线程创建: 如果没有空闲线程,并且当前线程数小于 maximumPoolSize,则创建新线程执行任务。任务队列: 如果当前线程数已经达到 maximumPoolSize,则将任务放入 SynchronousQueue 中等待。线程复用: 当一个线程执行完任务后,它不会立即终止,而是尝试从 SynchronousQueue 中获取新任务。线程回收: 如果线程在 keepAliveTime 时间内没有获取到新任务,它将被终止。这种设计使得 newCachedThreadPool 非常适合处理大量短生命周期的任务,因为它可以动态地调整线程数量以适应任务负载的变化。然而,由于它可以创建无限多的线程,如果没有适当的任务队列来控制任务的数量,可能会导致资源耗尽。因此,在使用 newCachedThreadPool 时,需要谨慎考虑任务的特性和系统的资源限制。使用场景:适用于执行大量短期异步任务,尤其是任务执行时间不确定的情况。例如,Web服务器处理大量并发请求,或者异步日志记录。2. newFixedThreadPool(int nThreads)newFixedThreadPool(int nThreads) 是 Java 中 java.util.concurrent 包的 Executors 类的一个静态工厂方法。这个方法用于创建一个固定大小的线程池,它能够确保线程池中始终有固定数量的线程在工作。以下是 newFixedThreadPool 方法的实现原理、源代码分析以及实现过程:实现原理固定线程数: 线程池中的线程数量始终保持为 nThreads。任务队列: 提交的任务首先由核心线程执行,如果核心线程都在忙碌状态,新任务将被放入一个阻塞队列中等待执行。线程复用: 线程池中的线程会重复利用,执行完一个任务后,会立即尝试从队列中获取下一个任务执行。源代码分析newFixedThreadPool 方法是通过调用 ThreadPoolExecutor 类的构造函数来实现的。以下是 ThreadPoolExecutor 构造函数的调用示例:public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor( nThreads, // 核心线程数 nThreads, // 最大线程数 0L, // 线程空闲时间,这里设置为0,表示线程不会空闲 TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() // 使用阻塞队列来存储任务 ); }参数解释:corePoolSize: 核心线程数,这里设置为 nThreads,表示线程池中始终有 nThreads 个线程。maximumPoolSize: 最大线程数,这里也设置为 nThreads,表示线程池的线程数量不会超过 nThreads。keepAliveTime: 当线程数大于核心线程数时,多余的空闲线程能等待新任务的最长时间,这里设置为0,表示如果线程池中的线程数超过核心线程数,这些线程将立即终止。unit: keepAliveTime 参数的时间单位,这里是毫秒。workQueue: 一个任务队列,这里使用的是 LinkedBlockingQueue,它是一个基于链表的阻塞队列,可以存储任意数量的任务。实现过程初始化: 当调用 newFixedThreadPool 时,会创建一个 ThreadPoolExecutor 实例。任务提交: 当任务提交给线程池时,线程池会检查是否有空闲的核心线程可以立即执行任务。任务队列: 如果所有核心线程都在忙碌状态,新提交的任务将被放入 LinkedBlockingQueue 中等待。线程复用: 核心线程执行完一个任务后,会尝试从 LinkedBlockingQueue 中获取新任务继续执行。线程数量控制: 由于 keepAliveTime 设置为0,当线程池中的线程数超过核心线程数时,这些线程会立即终止,从而保证线程池中的线程数量不会超过 nThreads。这种设计使得 newFixedThreadPool 非常适合处理大量且持续的任务,因为它可以保证任务以固定的线程数量并行执行,同时避免了线程数量的无限制增长。然而,由于线程池的大小是固定的,如果任务提交的速率超过了线程池的处理能力,可能会导致任务在队列中等待较长时间。因此,在使用 newFixedThreadPool 时,需要根据任务的特性和预期的负载来合理设置 nThreads 的值。使用场景:适用于执行大量长期运行的任务,其中线程数量需要固定。例如,同时运行多个数据加载或数据处理任务,且希望限制并发数以避免资源过载。3. newSingleThreadExecutor()newSingleThreadExecutor 是 Java 中 java.util.concurrent 包的 Executors 类的一个静态工厂方法,用于创建一个单线程的执行器。这个执行器确保所有任务都按照任务提交的顺序,在一个线程中顺序执行。以下是 newSingleThreadExecutor 方法的实现原理、源代码分析以及实现过程:实现原理单线程执行: 线程池中只有一个线程,所有任务都由这个线程顺序执行。任务队列: 如果这个线程在执行任务时有新任务提交,新任务会被放入一个阻塞队列中等待执行。线程复用: 这个线程会重复利用,执行完一个任务后,会立即尝试从队列中获取下一个任务执行。源代码分析newSingleThreadExecutor 方法同样是通过调用 ThreadPoolExecutor 类的构造函数来实现的。以下是 ThreadPoolExecutor 构造函数的调用示例:public static ExecutorService newSingleThreadExecutor() { return new ThreadPoolExecutor( 1, // 核心线程数 1, // 最大线程数 0L, TimeUnit.MILLISECONDS, // 线程空闲时间,这里设置为0,表示线程不会空闲 new LinkedBlockingQueue<Runnable>() // 使用阻塞队列来存储任务 ); }参数解释:corePoolSize: 核心线程数,这里设置为1,表示线程池中始终有一个核心线程。maximumPoolSize: 最大线程数,这里也设置为1,表示线程池的线程数量不会超过1。keepAliveTime: 线程空闲时间,这里设置为0,表示如果线程空闲,它将立即终止。unit: keepAliveTime 参数的时间单位,这里是毫秒。workQueue: 一个任务队列,这里使用的是 LinkedBlockingQueue,它是一个无界队列,可以存储任意数量的任务。实现过程初始化: 当调用 newSingleThreadExecutor 时,会创建一个 ThreadPoolExecutor 实例。任务提交: 当任务提交给线程池时,如果核心线程空闲,则立即执行任务;如果核心线程忙碌,则将任务放入 LinkedBlockingQueue 中等待。顺序执行: 由于只有一个线程,所有任务都将按照提交的顺序被执行。任务队列: 如果核心线程在执行任务,新提交的任务将被放入 LinkedBlockingQueue 中排队等待。线程复用: 核心线程执行完一个任务后,会尝试从 LinkedBlockingQueue 中获取新任务继续执行。线程数量控制: 由于 keepAliveTime 设置为0,核心线程在没有任务执行时会立即终止。但由于 corePoolSize 和 maximumPoolSize 都为1,线程池会立即重新创建一个线程。这种设计使得 newSingleThreadExecutor 非常适合处理需要保证任务顺序的场景,例如,当任务之间有依赖关系或者需要按照特定顺序执行时。同时,由于只有一个线程,这也避免了多线程环境下的并发问题。然而,由于只有一个线程执行任务,这也限制了并行处理的能力,如果任务执行时间较长,可能会导致后续任务等待较长时间。因此,在使用 newSingleThreadExecutor 时,需要根据任务的特性和对顺序的要求来决定是否适用。使用场景:适用于需要保证任务顺序执行的场景,例如,顺序处理队列中的消息或事件。也适用于需要单个后台线程持续处理周期性任务的情况。4. newScheduledThreadPool(int corePoolSize)newScheduledThreadPool 是 Java 中 java.util.concurrent 包的 Executors 类的一个静态工厂方法,用于创建一个固定大小的线程池,这个线程池支持定时以及周期性的任务执行。以下是 newScheduledThreadPool 方法的实现原理、源代码分析以及实现过程:实现原理定时任务: 线程池能够按照指定的延迟执行任务,或者以固定间隔周期性地执行任务。固定线程数: 线程池中的线程数量被限制为 corePoolSize 指定的大小。任务队列: 任务首先由核心线程执行,如果核心线程都在忙碌状态,新任务将被放入一个延迟任务队列中等待执行。源代码分析newScheduledThreadPool 方法是通过调用 ScheduledThreadPoolExecutor 类的构造函数来实现的。以下是 ScheduledThreadPoolExecutor 构造函数的调用示例:public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }这里的 ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的一个子类,专门为执行定时任务设计。ScheduledThreadPoolExecutor 构造函数的参数 corePoolSize 定义了线程池中核心线程的数量。ScheduledThreadPoolExecutor 内部使用了一个 DelayedWorkQueue 作为任务队列,这个队列能够按照任务的预定执行时间对任务进行排序。实现过程初始化: 当调用 newScheduledThreadPool 时,会创建一个 ScheduledThreadPoolExecutor 实例。任务提交: 当任务提交给线程池时,线程池会根据任务的预定执行时间,将任务放入 DelayedWorkQueue 中。任务调度: 线程池中的线程会从 DelayedWorkQueue 中获取任务,如果任务的执行时间已经到达,线程将执行该任务。线程复用: 执行完一个任务的线程会再次尝试从 DelayedWorkQueue 中获取下一个任务。线程数量控制: 如果任务队列中的任务数量超过了核心线程能够处理的范围,ScheduledThreadPoolExecutor 会创建新的线程来帮助处理任务,直到达到 corePoolSize 指定的最大线程数。特点ScheduledThreadPoolExecutor 允许设置一个线程工厂,用于创建具有特定属性的线程。它还允许设置一个 RejectedExecutionHandler,当任务无法被接受时(例如,线程池关闭或任务队列已满),这个处理器会被调用。与 ThreadPoolExecutor 不同,ScheduledThreadPoolExecutor 的 shutdown 和 shutdownNow 方法不会等待延迟任务执行完成。使用 newScheduledThreadPool 创建的线程池非常适合需要执行定时任务的场景,例如,定期执行的后台任务、定时检查等。然而,由于它是基于固定大小的线程池,所以在高负载情况下,任务可能会排队等待执行,这需要在设计时考虑适当的 corePoolSize 以满足性能要求。使用场景:适用于需要定期执行任务或在将来某个时间点执行任务的场景。例如,定时备份数据、定时发送提醒等。5. newWorkStealingPool(int parallelism)newWorkStealingPool 是 Java 8 中新增的 java.util.concurrent 包的 Executors 类的一个静态工厂方法。这个方法用于创建一个工作窃取(Work-Stealing)线程池,它能够提高并行任务的执行效率,特别是在多处理器系统上。实现原理工作窃取: 在工作窃取线程池中,每个线程都有自己的任务队列。当一个线程完成自己的任务后,它会尝试从其他线程的任务队列中“窃取”任务来执行。并行级别: 线程池的大小由 parallelism 参数决定,这个参数通常等于主机上的处理器核心数。动态调整: 工作窃取线程池可以动态地添加或移除线程,以适应任务的负载和线程的利用率。源代码分析newWorkStealingPool 方法是通过调用 ForkJoinPool 类的静态工厂方法 commonPoolFor 来实现的。以下是 ForkJoinPool 构造函数的调用示例:public static ExecutorService newWorkStealingPool(int parallelism) { return new ForkJoinPool( parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, // 没有未处理的异常处理器 false // 不是一个异步任务 ); }参数解释:parallelism: 线程池的并行级别,即线程池中的线程数量。ForkJoinPool.defaultForkJoinWorkerThreadFactory: 默认的线程工厂,用于创建线程。null: 未处理的异常处理器,这里没有指定,因此如果任务抛出未捕获的异常,它将被传播到 ForkJoinTask 的调用者。false: 表示这不是一个异步任务。ForkJoinPool 内部使用了 ForkJoinWorkerThread 来执行任务,并且每个线程都有一个 ForkJoinQueue 来存储任务。实现过程初始化: 当调用 newWorkStealingPool 时,会创建一个 ForkJoinPool 实例。任务提交: 当任务提交给线程池时,它们会被放入调用线程的本地队列中。任务执行: 每个线程首先尝试执行其本地队列中的任务。工作窃取: 如果本地队列为空,线程会尝试从其他线程的队列中窃取任务来执行。动态调整: 线程池可以根据需要动态地添加或移除线程。特点工作窃取线程池特别适合于工作量不均匀分布的任务,因为它可以减少空闲时间并提高资源利用率。它也适用于可分解为多个子任务的并行计算任务,因为可以将任务分解后,再将子任务提交给线程池。由于每个线程都有自己的队列,因此减少了锁的争用,提高了并发性能。使用 newWorkStealingPool 创建的线程池非常适合于需要高并发和高吞吐量的场景,尤其是在多处理器系统上。然而,由于工作窃取机制,它可能不适用于任务执行时间非常短或者任务数量非常少的场景,因为窃取任务本身可能会引入额外的开销。使用场景:适用于工作量不均匀或可分解为多个小任务的并行计算任务。例如,图像处理、数据分析等,可以在多核处理器上有效利用所有核心。6. newSingleThreadScheduledExecutor()newSingleThreadScheduledExecutor 是 Java 中 java.util.concurrent 包的 Executors 类的一个静态工厂方法。这个方法用于创建一个单线程的调度执行器,它可以安排命令在给定的延迟后运行,或者定期地执行。以下是 newSingleThreadScheduledExecutor 方法的实现原理、源代码分析以及实现过程:实现原理单线程执行: 执行器确保所有任务都在单个线程中顺序执行,这保证了任务的执行顺序。定时任务: 支持延迟执行和周期性执行任务。任务队列: 所有任务首先被放入一个任务队列中,然后由单线程按顺序执行。源代码分析newSingleThreadScheduledExecutor 方法是通过调用 ScheduledThreadPoolExecutor 类的构造函数来实现的。以下是 ScheduledThreadPoolExecutor 构造函数的调用示例:public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new ScheduledThreadPoolExecutor(1); }这里,ScheduledThreadPoolExecutor 是 ExecutorService 的一个实现,专门为执行定时任务设计。构造函数只有一个参数,即核心线程数,这里设置为1,表示这是一个单线程的执行器。ScheduledThreadPoolExecutor 内部使用了一个 DelayedWorkQueue 作为任务队列,这个队列能够按照任务的预定执行时间对任务进行排序。实现过程初始化: 当调用 newSingleThreadScheduledExecutor 时,会创建一个 ScheduledThreadPoolExecutor 实例,其核心线程数为1。任务提交: 当任务提交给执行器时,任务会被封装成 ScheduledFutureTask 或者 RunnableScheduledFuture,然后放入 DelayedWorkQueue 中。任务调度: 单线程会不断地从 DelayedWorkQueue 中获取任务,并按照预定的时间执行。如果任务的执行时间已经到达,任务将被执行;如果还没有到达,线程会等待直到执行时间到来。顺序执行: 由于只有一个线程,所有任务都将按照它们被提交的顺序被执行。周期性任务: 对于需要周期性执行的任务,执行器会在每次任务执行完毕后,重新计算下一次执行的时间,并再次将任务放入队列。特点newSingleThreadScheduledExecutor 创建的执行器非常适合需要保证任务顺序的场景,例如,需要按照特定顺序执行的任务或者具有依赖关系的任务。它也适合执行定时任务,如定期执行的维护任务或者后台任务。由于只有一个线程,这也避免了多线程环境下的并发问题,简化了任务同步和状态管理。使用 newSingleThreadScheduledExecutor 创建的执行器可以提供强大的定时任务功能,同时保持任务执行的顺序性。然而,由于只有一个线程执行任务,这也限制了并行处理的能力,如果任务执行时间较长,可能会导致后续任务等待较长时间。因此,在使用 newSingleThreadScheduledExecutor 时,需要根据任务的特性和对顺序的要求来决定是否适用。使用场景:适用于需要单个后台线程按计划执行任务的场景。例如,定时检查系统状态、定时执行维护任务等。7. privilegedThreadFactory()privilegedThreadFactory 是 Java 中 java.util.concurrent 包的 Executors 类的一个静态工厂方法,用于创建一个线程工厂,该工厂能够产生具有特权访问的线程。这意味着这些线程可以加载系统属性和库,并且可以访问文件系统。以下是 privilegedThreadFactory 方法的实现原理、源代码分析以及实现过程:实现原理特权访问: 创建的线程将具有访问系统资源的权限,例如,加载系统属性和库。线程创建: 线程工厂将创建新的线程实例,这些线程实例将继承创建它们的线程的上下文。源代码分析在 Java 的标准库中,privilegedThreadFactory 方法的实现细节并未公开,因为它是一个私有方法。然而,我们可以分析其大致工作原理。privilegedThreadFactory 方法的调用示例如下:public static ThreadFactory privilegedThreadFactory() { return new PrivilegedThreadFactory(); }这里,PrivilegedThreadFactory 是 Executors 类的一个私有静态内部类,它实现了 ThreadFactory 接口。ThreadFactory 接口定义了一个 newThread(Runnable r) 方法,用于创建新的线程。实现过程初始化: 当调用 privilegedThreadFactory 方法时,会返回一个新的 PrivilegedThreadFactory 实例。线程创建: 当使用这个工厂创建线程时,它会调用 newThread(Runnable r) 方法。特权访问: 在 newThread(Runnable r) 方法的实现中,会使用 AccessController.doPrivileged 方法来确保新创建的线程具有特权访问。上下文复制: 通常,新线程会复制创建它的线程的上下文,包括类加载器等。示例代码虽然我们不能查看 privilegedThreadFactory 的具体实现,但是我们可以提供一个示例实现,以展示如何创建具有特权访问的线程:public class PrivilegedThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { return AccessController.doPrivileged(new PrivilegedAction<>() { @Override public Thread run() { return new Thread(r); } }); } }在这个示例中,PrivilegedAction 是一个实现了 PrivilegedAction<T> 接口的匿名类,其 run 方法创建了一个新的线程。AccessController.doPrivileged 方法用于执行一个特权操作,这里是为了确保线程创建过程中具有必要的权限。特点使用 privilegedThreadFactory 创建的线程可以在需要访问敏感系统资源的情况下使用。这种线程工厂通常用于需要执行特权操作的应用程序,例如,访问系统属性或者执行文件 I/O 操作。使用 privilegedThreadFactory 可以确保线程在执行任务时具有适当的安全权限,从而避免安全异常。然而,需要注意的是,过度使用特权访问可能会带来安全风险,因此在设计应用程序时应谨慎使用。使用场景:适用于需要线程具有更高权限来访问系统资源的场景。例如,需要访问系统属性或执行文件I/O操作的应用程序。8. defaultThreadFactory()defaultThreadFactory 是 Java 中 java.util.concurrent 包的 Executors 类的一个静态工厂方法,用于创建一个默认的线程工厂。这个线程工厂生成的线程没有特殊的权限,它们是普通的线程,具有标准的访问权限。以下是 defaultThreadFactory 方法的实现原理、源代码分析以及实现过程:实现原理标准线程创建: 创建的线程工厂将生成具有默认属性的线程。线程名称: 生成的线程具有默认的线程名称前缀,通常是 "pool-x-thread-y",其中 x 和 y 是数字。线程优先级: 线程的优先级设置为 Thread.NORM_PRIORITY,这是 Java 线程的默认优先级。非守护线程: 创建的线程不是守护线程(daemon threads),它们的存在不会阻止 JVM 退出。源代码分析Java 的 defaultThreadFactory 方法的具体实现细节并未完全公开,因为它是 Executors 类的一个私有静态方法。但是,我们可以根据 Java 的 ThreadFactory 接口和一些公开的源代码片段来分析其大致实现。以下是 defaultThreadFactory 方法的调用示例:public static ThreadFactory defaultThreadFactory() { return new DefaultThreadFactory(); }这里,DefaultThreadFactory 是 Executors 类的一个私有静态内部类,它实现了 ThreadFactory 接口。ThreadFactory 接口定义了一个 newThread(Runnable r) 方法,用于创建新的线程。实现过程初始化: 当调用 defaultThreadFactory 方法时,会返回一个新的 DefaultThreadFactory 实例。线程创建: 使用这个工厂创建线程时,它会调用 newThread(Runnable r) 方法。设置线程名称: 在 newThread(Runnable r) 方法的实现中,会创建一个新的 Thread 对象,并设置一个默认的线程名称。设置线程组: 新线程会被分配到一个默认的线程组中。线程优先级和守护状态: 线程的优先级设置为默认值,且线程不是守护线程。示例代码虽然我们不能查看 defaultThreadFactory 的具体实现,但是我们可以提供一个示例实现,以展示如何创建具有默认属性的线程:public 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; } }在这个示例中,DefaultThreadFactory 使用 AtomicInteger 来确保线程池和线程编号的唯一性。创建的线程名称具有前缀 "pool-x-thread-y",其中 x 和 y 是自增的数字。线程不是守护线程,且优先级设置为 Thread.NORM_PRIORITY。特点使用 defaultThreadFactory 创建的线程工厂生成的线程具有标准的 Java 线程属性。这种线程工厂通常用于不需要特殊权限的应用程序。由于线程不是守护线程,它们的存在可以维持 JVM 的运行,直到所有非守护线程执行完毕。使用 defaultThreadFactory 可以确保线程在执行任务时具有标准的安全和执行属性,适合大多数常规用途。然而,如果应用程序需要特殊的线程属性,如守护线程或不同的优先级,可能需要自定义线程工厂。使用场景:适用于大多数标准应用程序,需要创建具有默认属性的线程。这是大多数 ExecutorService 实现的默认选择。9. unconfigurableExecutorService(ExecutorService executor)unconfigurableExecutorService 是 Java 中 java.util.concurrent 包的 Executors 类的一个静态工厂方法。这个方法用于创建一个不可配置的 ExecutorService 包装器,这意味着一旦包装后的 ExecutorService 被创建,就不能更改其配置,比如不能修改其线程池大小或任务队列等。以下是 unconfigurableExecutorService 方法的实现原理、源代码分析以及实现过程:实现原理封装: 将现有的 ExecutorService 封装在一个不可配置的代理中。不可修改: 所有修改配置的方法调用,如 shutdown, shutdownNow, setCorePoolSize 等,都将抛出 UnsupportedOperationException。转发: 除了配置修改的方法外,其他方法调用将被转发到原始的 ExecutorService。源代码分析unconfigurableExecutorService 方法的具体实现细节并未完全公开,因为它是 Executors 类的一个私有静态方法。但是,我们可以根据 Java 的 ExecutorService 接口和代理机制来分析其大致实现。以下是 unconfigurableExecutorService 方法的调用示例:public static ExecutorService unconfigurableExecutorService(ExecutorService executor) { return new FinalizableDelegatedExecutorService(executor); }这里,FinalizableDelegatedExecutorService 是 Executors 类的一个私有静态内部类,它实现了 ExecutorService 接口,并代理了对另一个 ExecutorService 的调用。实现过程初始化: 当调用 unconfigurableExecutorService 方法时,会返回一个新的 FinalizableDelegatedExecutorService 实例,它将原始的 ExecutorService 作为参数。方法调用拦截: 对 FinalizableDelegatedExecutorService 的方法调用将首先被拦截。配置修改拦截: 如果调用的方法是用于修改配置的,比如 shutdown 或 shutdownNow,将抛出 UnsupportedOperationException。转发其他调用: 对于其他不涉及配置修改的方法调用,比如 submit, execute, 将被转发到原始的 ExecutorService。示例代码下面V哥来模拟一个示例实现,以展示如何创建一个不可配置的 ExecutorService 代理:public class UnconfigurableExecutorService implements ExecutorService { private final ExecutorService executor; public UnconfigurableExecutorService(ExecutorService executor) { this.executor = executor; } @Override public void shutdown() { throw new UnsupportedOperationException("Shutdown not allowed"); } @Override public List<Runnable> shutdownNow() { throw new UnsupportedOperationException("Shutdown not allowed"); } @Override public boolean isShutdown() { return executor.isShutdown(); } @Override public boolean isTerminated() { return executor.isTerminated(); } @Override public void execute(Runnable command) { executor.execute(command); } // 其他 ExecutorService 方法的实现,遵循相同的模式 }在这个示例中,UnconfigurableExecutorService 拦截了 shutdown 和 shutdownNow 方法,并抛出了异常。其他方法则直接转发到原始的 ExecutorService。特点使用 unconfigurableExecutorService 创建的 ExecutorService 代理确保了线程池的配置不能被外部修改。这可以用于防止意外地更改线程池的状态,提高线程池使用的安全性。除了配置修改的方法外,其他所有方法都保持了原有 ExecutorService 的行为。使用 unconfigurableExecutorService 可以为现有的 ExecutorService 提供一个安全层,确保它们的状态不会被意外地更改。这对于在多线程环境中共享 ExecutorService 时特别有用。使用场景:适用于需要确保线程池配置在创建后不被更改的场景。例如,当多个组件共享同一个线程池时,可以防止一个组件意外修改配置转载自https://www.cnblogs.com/wgjava/p/18292258
  • [技术干货] 使用httpclient调用第三方接口返回javax.net.ssl.SSLHandshakeException异常
    1. 踩坑经历最近做了个需求,需要调用第三方接口获取数据,在联调时一直失败,代码抛出javax.net.ssl.SSLHandshakeException异常,具体错误信息如下所示:javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target2.原因分析因为调用第三方接口的代码是复用项目中原有的工具类(基于httpclient封装),所以在确认完传参没问题后,第一时间排除了编码问题。然后开始怀疑第三方提供的接口地址(因为竟然是IP+端口访问),在和第三方确认没有域名访问后,在浏览器里输入第三方的接口地址,发现证书有问题:又使用Postman调用第三方接口,也是失败,提示自签名证书:通过以上分析,可以发现出现该问题的根本原因是Java客户端不信任目标服务器的SSL证书,比如这个第三方使用的自签名证书。3.解决方案解决方案一般有2种,第1种方案是将服务器证书导入Java信任库,第2种方案是绕过SSL验证,这里采用第2种方案。首先,新建HttpClient工具类:import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; public class HttpClientUtils { public static CloseableHttpClient createIgnoreCertClient() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, new TrustManager[]{new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { } }}, new java.security.SecureRandom()); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build(); } }然后将原来声明httpClient的代码改为如下所示:CloseableHttpClient httpClient = HttpClientUtils.createIgnoreCertClient();注意事项:确保项目中引入了httpclient依赖:<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency>转载自https://www.cnblogs.com/zwwhnly/p/18795523
  • [大赛资讯] 本地extra数据集正常交互,用时78秒,提交线上以后有时候是unexpected_eoln有时候是time_out
    这种情况是不是因为代码内部逻辑卡死,偶尔崩溃偶尔超时的原因呀?
  • [大赛资讯] 依然出现runtime_error问题
    在本地判题器跑没有问题,提交代码后就出现runtime_error问题,好难排bug
  • [大赛资讯] disk_head_action_error,请问这个报错一般是什么原因呢
    本地的三个样例都没问题
  • [大赛资讯] 样例代码预编译过不去
    我的代码在控制台手打样例可以正常跑,但本地编译卡在预编译,连ok都出不来,甚至用样例代码跑了也是这个问题
  • [问题求助] 【华为云cce 】怎么使用自建server实现k8s集群的的web-terminal
    建立websocket连接,进入到集群内部,通过下发自定义指令在集群内部执行
  • [大赛资讯] 【区域初赛赛题问题】 本地编译通过,提交后编译失败 compile_error
    本地正常运行,提交后报错编译错误 compile_error
  • [大赛资讯] 【区域初赛赛题问题】 本地编译通过,提交后编译失败
    本地可以正常运行,提交系统后报编译错误,能否帮忙排查~
  • [大赛资讯] 请问如何得知unexpected_elon出现的报错是内存超了还是程序超时了?
    请问如何得知unexpected_elon出现的报错是内存超了还是程序超时了?
  • [互动交流] 开始《Java智能助手设置》报错,不知道在哪设置
    命令"SmartAssist: Java智能助手设置"导致错误 (command 'java-smart-assist.settings.open' not found)
  • [大赛资讯] 咋改都是runtime_error, 做不了一点
    本地跑过数据不知道几次了,限制内存也能跑过,但是交上去就会 runtime_error。这是 debug 大赛吗 ?
  • [大赛资讯] 怎么该都是runtime_error
    为什么本地的两个数据都可以跑过,但是一交上去就runtime_error?
  • [技术干货] java的线程
    在 Java 中,线程是程序执行的最小单元。Java 提供了多线程编程的支持,允许你同时运行多个线程,从而提高程序的并发性和效率。每个线程都有自己的执行路径,可以独立执行任务。Java 中创建线程有两种主要方式:继承 Thread 类:通过继承 Thread 类并重写 run() 方法来定义线程的执行逻辑。实现 Runnable 接口:通过实现 Runnable 接口并将其传递给 Thread 类的构造函数来创建线程。实践示例1. 继承 Thread 类java复制class MyThread extends Thread { @Override public void run() { // 线程执行的代码 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); } }}public class ThreadExample { public static void main(String[] args) { MyThread thread1 = new MyThread(); MyThread thread2 = new MyThread(); // 启动线程 thread1.start(); thread2.start(); }}解释:MyThread 类继承了 Thread 类,并重写了 run() 方法。在 main 方法中,创建了两个 MyThread 对象 thread1 和 thread2,并调用 start() 方法启动线程。start() 方法会调用 run() 方法,线程开始执行。2. 实现 Runnable 接口java复制class MyRunnable implements Runnable { @Override public void run() { // 线程执行的代码 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); } }}public class RunnableExample { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread1 = new Thread(myRunnable); Thread thread2 = new Thread(myRunnable); // 启动线程 thread1.start(); thread2.start(); }}解释:MyRunnable 类实现了 Runnable 接口,并实现了 run() 方法。在 main 方法中,创建了一个 MyRunnable 对象 myRunnable,然后将其传递给 Thread 类的构造函数,创建了两个线程 thread1 和 thread2。调用 start() 方法启动线程。线程的生命周期线程的生命周期包括以下几个状态:新建(New):线程对象被创建,但尚未启动。就绪(Runnable):线程已经启动,等待 CPU 调度执行。运行(Running):线程正在执行 run() 方法中的代码。阻塞(Blocked):线程因为某些原因(如等待资源)暂时停止执行。终止(Terminated):线程执行完毕或异常退出。线程的常用方法start():启动线程,使其进入就绪状态。run():线程执行的主体方法。sleep(long millis):使当前线程暂停执行指定的毫秒数。join():等待该线程终止。interrupt():中断线程。isAlive():检查线程是否处于活动状态。线程同步在多线程环境中,可能会出现多个线程同时访问共享资源的情况,导致数据不一致的问题。Java 提供了同步机制来解决这个问题,常用的同步方式有:synchronized 关键字:用于修饰方法或代码块,确保同一时间只有一个线程执行该代码。Lock 接口:提供了更灵活的锁机制。总结Java 线程是实现并发编程的基础。通过继承 Thread 类或实现 Runnable 接口,你可以创建并启动线程。理解线程的生命周期和同步机制对于编写高效、安全的多线程程序至关重要
总条数:764 到第
上滑加载中