Java 多线程深度剖析与实战指南

一、前言

在 Java 的世界里,多线程编程一直是开发人员眼中既神秘又充满挑战的领域。从简单的线程创建与启动,到复杂的线程安全问题和性能优化,多线程编程贯穿了 Java 应用开发的始终。本文将深入浅出地剖析 Java 多线程的核心概念、原理和实战技巧。

二、多线程基础

(一)线程与进程

  1. 进程 进程是具有一定独立功能的程序关于某个数据集合的一次运行活动,是操作系统动态执行的基本单元。在操作系统中,进程既是基本的分配单元,也是基本的执行单元。例如,当你同时运行游戏和网易云音乐时,这两个程序分别对应着两个不同的进程。
  1. 线程 线程是进程中的一个实体,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源,如内存地址空间、文件句柄等。线程是独立调度和执行的最小单位。比如在 IDEA 中编写代码时,语法检查和自动保存功能就是多线程操作的体现。

(二)线程的生命周期

Java 中的线程有 6 种状态,分别是新建(NEW)、运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、计时等待(TIMED_WAITING)和终止(TERMINATED)。其中,新建状态表示线程已被创建但尚未启动;运行状态表示线程正在 JVM 中执行;阻塞状态指线程被阻塞,无法获取锁;等待状态是线程在等待其他线程的通知;计时等待状态类似于等待状态,但有一个等待时间限制;终止状态表示线程已执行完毕。

(三)并发与并行

  1. 并发 并发指的是多个线程共享同一资源,交替执行。例如在秒杀抢购活动中,多个用户同时对一个商品发起抢购请求,这些请求对应的线程就是并发执行的。
  1. 并行 并行则指多项工作同时执行,之后再汇总结果。比如泡方便面时,电水壶烧水和撕调料倒入桶中这两个操作就可以并行进行。

(四)wait 与 sleep 的区别

  1. waitwait()Object 类的方法,调用该方法的线程会释放锁,进入等待状态,直到其他线程调用 notify()notifyAll() 方法通知它。
  1. sleepsleep()Thread 类的静态方法,调用该方法的线程在指定时间内暂停执行,但不会释放锁。

三、JUC(Java Util Concurrency)概述

JUC 是 JDK 5 引入的高并发工具类的集合,位于 java.util.concurrent 包下,主要包括锁、并发集合、并发工具类等,为解决多线程编程中的各种问题提供了强大的支持。

四、锁机制

(一)synchronized

synchronized 是 Java 内置的关键字,用于实现同步操作。它可以修饰方法、代码块等,其核心原理是利用 Java 中的每一个对象都可以作为锁。例如,对于普通同步方法,锁是当前实例对象;对于静态同步方法,锁是当前类的 Class 对象;对于同步代码块,锁是括号里面配置的对象。以下是 synchronized 的一个案例:
class Phone { public static synchronized void sendEmail() throws InterruptedException { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "----Phone.sendEmail"); } public synchronized void sendSMS() { System.out.println(Thread.currentThread().getName() + "----Phone.sendSMS"); } //普通方法 public String sayHello() { return Thread.currentThread().getName() + "----Hello Thread......"; } } public class LockDemo { public static void main(String[] args) throws Exception { //资源类 Phone phone = new Phone(); Phone phone2 = new Phone(); //创建线程 new Thread(() -> { try { phone.sendEmail(); } catch (InterruptedException e) { e.printStackTrace(); } }, "A").start(); //第二条线程 new Thread(() -> { phone.sendSMS(); }, "B").start(); //第三条线程 new Thread(() -> { System.out.println(phone.sayHello()); }, "C").start(); } }

(二)Lock 接口

Lock 接口是 JDK 5 引入的新的多线程锁,提供了比 synchronized 更加灵活的锁机制。ReentrantLockLock 接口的实现类,它支持可重入、可公平等多种锁机制。以下是 ReentrantLock 的一个案例:
class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } }
synchronized 相比,Lock 有以下区别:
  1. synchronized 是 Java 内置关键字,在 JVM 层面,Lock 是个 Java 类。
  1. synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁。
  1. synchronized 会自动释放锁,Lock 需在 finally 中手工释放锁,否则容易造成线程死锁。
  1. synchronized 的锁不可中断,Lock 的锁可中断。
  1. synchronized 的锁是非公平的,Lock 的锁可公平可非公平。

五、创建线程的方式

在 Java 中,创建线程主要有以下几种方式:
  1. 继承 Thread 类
public class MyThread extends Thread { @Override public void run() { // 线程执行的代码 } } Thread thread = new MyThread(); thread.start();
  1. 实现 Runnable 接口
class MyThread implements Runnable { @Override public void run() { // 线程执行的代码 } } new Thread(new MyThread()).start();
  1. 使用 Lambda 表达式
new Thread(() -> { // 线程执行的代码 }, "Thread Name").start();
  1. 使用 FutureTask 类
FutureTask<String> task = new FutureTask<>(new Callable<String>() { @Override public String call() throws Exception { return "Callable线程..."; } }); new Thread(task, "A").start(); String s = task.get(); System.out.println("s = " + s);
  1. 使用自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy() );

六、线程间通信

(一)线程通信的方式

线程间通信主要通过生产者 - 消费者模式和通知等待唤醒机制来实现。生产者 - 消费者模式是一种典型的多线程协作模式,生产者负责生产数据,消费者负责消费数据。通知等待唤醒机制则是通过 wait()notify()notifyAll() 等方法来实现线程之间的通信。

(二)多线程编程模板

多线程编程模板主要包括判断、干活和通知三个步骤。以下是一个基于 synchronized 实现的线程间通信的案例:
class ShareDataOne { private int number = 0; public synchronized void increment() throws InterruptedException { while (number != 0) { this.wait(); } ++number; System.out.println(Thread.currentThread().getName() + "\\t" + number); this.notifyAll(); } public synchronized void decrement() throws InterruptedException { while (number == 0) { this.wait(); } --number; System.out.println(Thread.currentThread().getName() + "\\t" + number); this.notifyAll(); } } public class NotifyWaitDemoOne { public static void main(String[] args) { ShareDataOne sd = new ShareDataOne(); new Thread(() -> { for (int i = 1; i < 10; i++) { try { sd.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 1; i < 10; i++) { try { sd.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); } }
在 JDK 8 中,还可以使用 Condition 来实现线程间通信,它提供了比 wait()notify() 更加灵活的通信方式:
class Aircondition { private int number = 0; private Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void decrement() { lock.lock(); try { while (number == 0) { condition.await(); } number--; System.out.println(Thread.currentThread().getName() + "\\t" + number); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void increment() { lock.lock(); try { while (number != 0) { condition.await(); } number++; System.out.println(Thread.currentThread().getName() + "\\t" + number); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public class ProdConsumerDemo { public static void main(String[] args) { Aircondition aircondition = new Aircondition(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { TimeUnit.SECONDS.sleep(1); aircondition.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { TimeUnit.SECONDS.sleep(1); aircondition.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { TimeUnit.SECONDS.sleep(1); aircondition.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { TimeUnit.SECONDS.sleep(1); aircondition.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } }

七、多线程锁

在多线程编程中,锁是实现线程安全的关键。以下是关于多线程锁的一些要点:
  1. 一个对象中的多个 synchronized 方法,某一时刻内只能有一个线程访问这些方法,因为锁的是当前对象 this
  1. 静态同步方法的锁是当前类的 Class 对象。
  1. 普通同步方法与静态同步方法之间互不影响,因为它们使用的锁不同。

八、JUC 之集合

(一)集合中的不安全类

  1. ListArrayList 在迭代时如果同时对其进行修改,会抛出 ConcurrentModificationException 异常。解决方法包括使用 VectorCollections.synchronizedListCopyOnWriteArrayList
  1. SetHashSet 是线程不安全的,可以使用 CopyOnWriteArraySet 来解决线程安全问题。
  1. MapHashMap 也是线程不安全的,ConcurrentHashMap 则提供了线程安全的解决方案。

(二)CopyOnWriteArrayList 原理

CopyOnWriteArrayList 是基于写时复制(CopyOnWrite)机制的线程安全集合。它的核心原理是,在对集合进行写操作(如添加、删除等)时,不会直接修改原集合,而是先创建一个原集合的副本,在副本上进行修改操作,修改完成后,再将原集合的引用指向新的副本。这样,在整个写操作过程中,读操作可以不受影响地在原集合上进行,从而实现了读写分离,保证了线程安全。以下是 CopyOnWriteArrayList 的部分源码:
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }

九、Callable 接口与 FutureTask 类

(一)Callable 接口

  1. 简介Callable 接口是 Java 5 引入的,它与 Runnable 接口类似,但比 Runnable 更强大。Callable 接口允许线程执行的任务返回一个结果,并且可以抛出异常。它只有一个方法 call(),该方法定义了线程执行的任务,可以返回一个结果,并且可以抛出受检异常。
  1. 与 Runnable 接口的区别
      • 是否有返回值:Callablecall() 方法可以返回结果,而 Runnablerun() 方法没有返回值。
      • 是否抛异常:Callablecall() 方法可以抛出受检异常,而 Runnablerun() 方法不能抛出受检异常。
      • 落地方法不一样:Callable 的落地方法是 call(),而 Runnable 的落地方法是 run()

(二)FutureTask 类

  1. 简介FutureTask 是 Java 5 引入的类,它实现了 RunnableFuture 接口,代表一个异步计算的任务。它提供了对异步任务的执行和结果获取的支持。你可以将 FutureTask 提交给线程池执行,也可以直接启动一个线程来执行它。
  1. 原理 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 FutureTask 在后台完成。当主线程将来需要时,就可以通过 FutureTaskget() 方法获取后台作业的计算结果或者执行状态。get() 方法会阻塞,直到任务完成。以下是 FutureTask 的一个案例:
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; public class CallableDemo { public static void main(String[] args) throws Exception { FutureTask<Integer> futureTask = new FutureTask<>(() -> { System.out.println(Thread.currentThread().getName() + " come in callable"); TimeUnit.SECONDS.sleep(4); return 1024; }); FutureTask<Integer> futureTask2 = new FutureTask<>(() -> { System.out.println(Thread.currentThread().getName() + " come in callable"); TimeUnit.SECONDS.sleep(4); return 2048; }); new Thread(futureTask, "zhang3").start(); new Thread(futureTask2, "li4").start(); while (!futureTask.isDone()) { System.out.println("***wait"); } System.out.println(futureTask.get()); System.out.println(Thread.currentThread().getName() + " come over"); } }

十、JUC 强大的辅助类

(一)CountDownLatch(减少计数)

  1. 原理CountDownLatch 是一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。它主要有两个方法,await()countDown()。当一个或多个线程调用 await() 方法时,这些线程会阻塞。其他线程调用 countDown() 方法会将计数器减 1,当计数器的值变为 0 时,因 await() 方法阻塞的线程会被唤醒,继续执行。
  1. 案例实现
public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; i <= 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\\t 号同学离开教室"); countDownLatch.countDown(); }, String.valueOf(i)).start(); } countDownLatch.await(); System.out.println(Thread.currentThread().getName() + "\\t****** 班长关门走人,main 线程是班长"); } }

(二)CyclicBarrier(循环栅栏)

  1. 原理CyclicBarrier 是一个同步辅助类,它允许一组线程互相等待,直到到达一个共同的屏障点。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过 CyclicBarrierawait() 方法。
  1. 案例实现
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierDemo { private static final int NUMBER = 7; public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> { System.out.println("*****集齐 7 颗龙珠就可以召唤神龙"); }); for (int i = 1; i <= 7; i++) { new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "\\t 星龙珠被收集 "); cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }, String.valueOf(i)).start(); } } }

(三)Semaphore(信号灯)

  1. 原理Semaphore 是一个计数信号量,它可以控制同时访问特定资源的线程数量。在信号量上定义两种操作:acquire(获取)和 release(释放)。当一个线程调用 acquire 操作时,它要么通过成功获取信号量(信号量减 1),要么一直等下去,直到有线程释放信号量,或超时。release 操作实际上会将信号量的值加 1,然后唤醒等待的线程。信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
  1. 案例实现
import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore = new Semaphore(3); // 模拟 3 个停车位 for (int i = 1; i <= 6; i++) { // 模拟 6 部汽车 new Thread(() -> { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + "\\t 抢到了车位"); TimeUnit.SECONDS.sleep(new Random().nextInt(5)); System.out.println(Thread.currentThread().getName() + "\\t------- 离开"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } }, String.valueOf(i)).start(); } } }

(四)ReentrantReadWriteLock(读写锁)

  1. 实际案例 在缓存框架中,读写锁可以很好地控制缓存的读写操作。例如,当多个线程同时读取缓存时,可以允许多个线程同时进行读操作;而当有线程需要写入缓存时,只允许一个线程进行写操作,并且此时不允许其他线程进行读写操作。
  1. 案例实现
import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; class MyCache { private volatile Map<String, Object> map = new HashMap<>(); private ReadWriteLock rwLock = new ReentrantReadWriteLock(); public void put(String key, Object value) { rwLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "\\t 正在写 " + key); try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } map.put(key, value); System.out.println(Thread.currentThread().getName() + "\\t 写完了 " + key); System.out.println(); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.writeLock().unlock(); } } public Object get(String key) { rwLock.readLock().lock(); Object result = null; try { System.out.println(Thread.currentThread().getName() + "\\t 正在读 " + key); try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } result = map.get(key); System.out.println(Thread.currentThread().getName() + "\\t 读完了 " + result); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.readLock().unlock(); } return result; } } public class ReadWriteLockDemo { public static void main(String[] args) { MyCache myCache = new MyCache(); for (int i = 1; i <= 5; i++) { final int num = i; new Thread(() -> { myCache.put(num + "", num + ""); }, String.valueOf(i)).start(); } for (int i = 1; i <= 5; i++) { final int num = i; new Thread(() -> { myCache.get(num + ""); }, String.valueOf(i)).start(); } } }

十一、BlockingQueue(阻塞队列)

(一)阻塞队列简介

阻塞队列是一种特殊的队列,当队列为空时,获取元素的线程会自动阻塞,直到有元素被添加到队列中;当队列已满时,添加元素的线列会自动阻塞,直到有元素被移出队列。Java 中的阻塞队列位于 java.util.concurrent.BlockingQueue 接口下,常见的实现类有 ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueueDelayQueueSynchronousQueueLinkedTransferQueueLinkedBlockingDeque 等。

(二)核心方法介绍

阻塞队列的核心方法主要包括以下几类:
  1. 插入元素
      • offer(E e):将元素插入队列,如果队列已满,返回 false
      • offer(E e, long timeout, TimeUnit unit):将元素插入队列,在指定时间内等待插入成功,如果超时则返回 false
      • put(E e):将元素插入队列,如果队列已满,阻塞等待。
  1. 移除元素
      • poll():移除队列中的头元素,如果队列为空,返回 null
      • poll(long timeout, TimeUnit unit):移除队列中的头元素,在指定时间内等待移除成功,如果超时则返回 null
      • take():移除队列中的头元素,如果队列为空,阻塞等待。
  1. 获取元素
      • peek():获取队列中的头元素,但不移除它,如果队列为空,返回 null

十二、线程池

(一)线程池的优点

  1. 降低资源消耗 通过重复利用已创建的线程,降低线程创建和销毁造成的资源消耗。
  1. 提高响应速度 当任务到达时,任务可以不需要等待线程创建就能立即执行。
  1. 提高线程的可管理性 线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

(二)线程池架构

Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor、Executors、ExecutorService、ThreadPoolExecutor 这几个类。以下是 JDK 默认的几种线程池创建方法:
  1. newFixedThreadPool(int nThreads) 创建一个固定大小的线程池,线程池中的线程数量固定,所有任务都被放入一个无界任务队列中。当线程池中的线程都在执行任务时,新的任务会在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
  1. newSingleThreadExecutor() 创建一个单线程的线程池,该线程池只有一个线程,所有任务都被依次执行。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
  1. newCachedThreadPool() 创建一个大小不固定的线程池,线程池的大小可以动态增加。当线程空闲时间超过 60 秒时,线程会被自动回收。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }

(三)ThreadPoolExecutor 底层原理

ThreadPoolExecutor 是 Java 中线程池的核心实现类,它通过一组参数来控制线程池的行为。以下是 ThreadPoolExecutor 的构造方法:
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 = getAccumulator(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
其主要参数包括:
  1. corePoolSize :线程池中的常驻核心线程数。
  1. maximumPoolSize :线程池中能够容纳同时执行的最大线程数。
  1. keepAliveTime :多余的空闲线程的存活时间。
  1. unit :keepAliveTime 的单位。
  1. workQueue :任务队列,被提交但尚未被执行的任务。
  1. threadFactory :生成线程池中工作线程的线程工厂。
  1. handler :拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程数时如何拒绝请求执行的 runnable 的策略。

(四)线程池的工作原理图

线程池的工作原理如下:
sequenceDiagram participant MainThread participant ThreadPoolExecutor participant WorkerThread participant TaskQueue MainThread->>ThreadPoolExecutor: 创建线程池(corePoolSize, maxPoolSize, keepAliveTime, ...) MainThread->>ThreadPoolExecutor: submit(task1) ThreadPoolExecutor->>WorkerThread1: 启动线程并执行 task1 MainThread->>ThreadPoolExecutor: submit(task2) ThreadPoolExecutor->>WorkerThread2: 启动线程并执行 task2 MainThread->>ThreadPoolExecutor: submit(task3) ThreadPoolExecutor->>TaskQueue: 将 task3 放入队列 MainThread->>ThreadPoolExecutor: submit(task4) ThreadPoolExecutor->>TaskQueue: 将 task4 放入队列 ThreadPoolExecutor->>WorkerThread3: 从队列中取出 task3 并执行 MainThread->>ThreadPoolExecutor: submit(task5) ThreadPoolExecutor->>TaskQueue: 将 task5 放入队列 ThreadPoolExecutor->>WorkerThread4: 从队列中取出 task4 并执行 WorkerThread1->>ThreadPoolExecutor: 任务执行完成 WorkerThread1->>TaskQueue: 从队列中取出 task5 并执行 MainThread->>ThreadPoolExecutor: submit(task6) ThreadPoolExecutor->>WorkerThread1: 拒绝任务(根据拒绝策略)
  1. 在创建了线程池后,开始等待请求。
  1. 当调用 execute() 方法添加一个请求任务时,线程池会做出如下判断:
      • 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务。
      • 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
      • 如果这个时候队列满了且正在运行的线程数量还小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务。
      • 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
  1. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
  1. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。

(五)线程池的拒绝策略

JDK 内置了四种拒绝策略:
  1. AbortPolicy(默认) :直接抛出 RejectedExecutionException 异常阻止系统正常运行。
  1. CallerRunsPolicy :“调用者运行” 一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  1. DiscardOldestPolicy :抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
  1. DiscardPolicy :该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。

(六)自定义线程池

可以使用 ThreadPoolExecutor 类来自定义线程池,以下是一个示例:
import java.util.concurrent.*; public class MyThreadPoolDemo { public static void main(String[] args) { ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy() ); try { for (int i = 1; i <= 10; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\\t 办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } }

十三、总结

Java 多线程编程是 Java 开发中的核心技术之一,它涉及到线程基础、锁机制、线程间通信、并发工具类等多个方面的知识。通过深入理解多线程的核心原理,熟练掌握 JUC 包中的各类工具类和工具方法,结合线程池等技术,我们可以高效地解决各种复杂的并发问题,提升系统的性能和可靠性。在实际开发中,要根据具体的应用场景合理地选择和使用多线程技术,避免过度设计或滥用,从而实现高效、稳定的并发程序。
 
云效Flow 自定义AI代码审核步骤Spring Boot加载与Bean处理的细节剖析
Loading...