首页
壁纸
统计
友链
Search
1
Vue2详细笔记
68 阅读
2
ExpressAPI
34 阅读
3
微信小程序
31 阅读
4
Spring MVC 请求生命周期
30 阅读
5
Nestjs概述-中文
29 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
40
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
20
篇与
的结果
2026-06-02
Java随心记
一、常用工具与集合操作1.1 Arrays.asList()作用:将数组转换为 List。注意:返回的 List 是固定大小的,不支持 add/remove 操作。List<String> list = Arrays.asList("a", "b", "c");1.2 CollUtil.toMap()(Hutool 工具)把一个 List 转换成 Map。参数1:要转换的列表。参数2:key 的生成方式(如 Stock::getSkuId)。参数3:value 的生成方式,Function.identity() 表示用对象本身作为 value。Map<Long, Stock> map = CollUtil.toMap(stocks, Stock::getSkuId, Function.identity());1.3 IdentityHashMap比较冷门,判断 key 是否相同依据 对象的引用地址(==),而不是 equals() / hashCode()。二、数据查询策略2.1 联表查询 vs 分开查 + Map 组装方式适用场景联表查询数据量不大、关联简单、不需要对查出的用户数据做额外处理分开查 + Map 组装数据分布在不同的服务器、微服务架构,或者需要灵活组装三、Java 集合框架3.1 集合体系概览Collection 是除 Map 外所有集合类的根接口。java.util 包主要提供三种类型的集合:List、Set、Map。统一使用 Iterator 遍历集合。3.2 ListArrayList:基于动态数组,内存连续。LinkedList:基于双向链表,节点有前后指针。List 允许添加 null。使用 List.of() 创建不可变 List(不支持 null 元素)。List<Integer> list = List.of(1, 2, 5);Iterator 遍历:boolean hasNext() 判断是否有下一个元素。E next() 返回下一个元素。boolean contains(Object o) 判断是否包含某元素。int indexOf(Object o) 返回元素索引,不存在返回 -1。重要:使用 contains()、indexOf() 等方法时,元素类必须正确覆写 equals()。String、Integer 等标准库类已经正确实现了 equals(),所以可以直接使用。3.3 编写 equals() 方法理论原则:自反性、对称性、传递性、一致性。实践写法(直接套用):先确定实例相等的逻辑(哪些字段相等就算相等)。用 instanceof 判断类型,类型匹配后再比较字段。使用 Objects.equals() 比较引用类型字段,避免空指针。public boolean equals(Object o) { if (o instanceof Person p) { return Objects.equals(this.name, p.name) && this.age == p.age; } return false; }3.4 MapMap 遍历是无序的。底层是哈希表,通过哈希函数将 key 转换成数组下标,直接定位,查找速度极快 O(1)。作为 key 的对象必须正确覆写 equals() 和 hashCode(),相等的 key 调用 equals 必须返回 true。3.5 Queue 队列实现先进先出(FIFO)的数据结构。常用方法:add() / offer():添加元素到队尾。remove() / poll():从队首获取并删除元素。element() / peek():从队首获取但不删除。避免将 null 添加到队列。四、编码与安全4.1 URL 编码使用 java.net.URLEncoder 对任意字符串进行 URL 编码。4.2 哈希算法目的:验证原始数据是否被篡改。MessageDigest 常见用途:计算文件校验和实现文件秒传存储用户密码防止数据重复生成 API 签名4.3 AES 加密最可靠的 AES 加密模式:AES-GCM。五、多线程与并发5.1 线程基础守护线程(Daemon)JVM 退出时不等待守护线程结束,直接强制终止。非守护线程(用户线程)结束时 JVM 才会退出。设置方法:在 start() 前调用 setDaemon(true)。核心业务线程不能设置为守护线程。典型用途:垃圾回收、日志监控、心跳检测。t.join()让当前线程等待 t 线程执行结束。生产环境禁用,容易造成阻塞,推荐用 Future 等异步方式。Thread.sleep(long millis)静态方法,哪个线程调用,哪个线程睡觉。线程中断线程结束即 run() 方法执行完毕。目标线程通过检查 isInterrupted() 标志判断自身是否已被中断。volatile 与内存可见性变量的值保存在主内存,线程访问时会拷贝到工作内存。修改后的值何时回写主内存不确定,因此会出现可见性问题。synchronized 不能修饰变量,要保证变量跨线程可见性,使用 volatile。注意:volatile 只能保证可见性和禁止指令重排,不保证原子性。5.2 同步机制synchronizedJava 语言层面语法,不需要手动释放锁(退出同步块自动释放)。修饰方法时,锁对象是 this(实例方法)或类对象(静态方法)。配合 wait() / notifyAll() 实现线程等待/唤醒(了解即可,实际多用 Lock 体系)。ReentrantLockJava 代码实现的锁,必须在 finally 中手动释放。标准用法:private final Lock lock = new ReentrantLock(); lock.lock(); try { // 同步代码 } finally { lock.unlock(); }配合 Condition 的 await() / signalAll() 实现等待/通知。ReadWriteLock提升读多写少场景的并发性能。规则:读-读 可共存写-写 互斥读-写 互斥(要么全是读,要么只有一个写)实现:private final ReadWriteLock rwlock = new ReentrantReadWriteLock(); private final Lock rlock = rwlock.readLock(); private final Lock wlock = rwlock.writeLock(); // 写操作 wlock.lock(); try { ... } finally { wlock.unlock(); } // 读操作 rlock.lock(); try { ... } finally { rlock.unlock(); }注意:读锁是悲观锁,频繁读可能导致写线程饥饿。StampedLock三种模式:写锁、悲观读锁、乐观读。乐观读 tryOptimisticRead() 完全不阻塞写线程,先获取版本号再读数据,读完后验证版本号有无变化,变化则重试或升级为悲观读。不可重入(一个线程持有写锁再尝试获取读锁会死锁)。Semaphore信号量,限制同时访问某个资源的线程数量。用法:final Semaphore semaphore = new Semaphore(3); // 最多3个许可 public String access() throws Exception { semaphore.acquire(); // 获取许可,可能阻塞 try { // 业务逻辑 } finally { semaphore.release(); // 释放许可 } }tryAcquire(long timeout, TimeUnit unit) 可指定等待时间。5.3 线程安全集合接口非线程安全线程安全替代ListArrayListCopyOnWriteArrayListMapHashMapConcurrentHashMapSetHashSet / TreeSetCopyOnWriteArraySetQueueArrayDeque / LinkedListArrayBlockingQueue / LinkedBlockingQueueDequeArrayDeque / LinkedListLinkedBlockingDeque5.4 线程池ExecutorService经典用法:ExecutorService executor = Executors.newFixedThreadPool(3); executor.submit(task1); ... executor.shutdown(); // 等待任务完成后关闭常用线程池及底层原理:FixedThreadPool:核心线程数 = 最大线程数 + 无界队列CachedThreadPool:核心线程数 0,最大线程数无限 + 同步移交队列SingleThreadExecutor:核心和最大线程数均为 1 + 无界队列ScheduledThreadPool:ScheduledThreadPoolExecutor,支持定时调度生产推荐:直接使用 ThreadPoolExecutor 定制参数,队列必须有界。public ThreadPoolExecutor( int corePoolSize, // CPU密集:n+1 IO密集:2n int maximumPoolSize, // ≥ corePoolSize long keepAliveTime, // 非核心线程空闲存活时间,常用30-60s TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 有界队列,如 ArrayBlockingQueue ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 )拒绝策略策略行为AbortPolicy(默认)丢弃任务,抛 RejectedExecutionExceptionCallerRunsPolicy由提交任务的线程自行执行,可减缓提交速度(推荐)DiscardPolicy静默丢弃任务,不抛异常DiscardOldestPolicy丢弃队列最老的任务,重新提交当前任务推荐配置:CallerRunsPolicy + 监控告警。注意事项创建线程只有一种方式:new Thread().start()。生产环境只用线程池提交任务,禁止手动 new Thread()。for 循环中调用 t.start() 会立即返回,不会等待线程 run() 执行完。5.5 Future 与异步编程Callable 与 Runnable 的区别:Callable 有返回值,可抛出异常。提交 Callable 返回 Future<V>。get():获取结果(可能阻塞)get(long timeout, TimeUnit unit):限时获取cancel(boolean mayInterruptIfRunning):取消任务isDone():判断任务是否完成CompletableFuture实现 Future 和 CompletionStage,支持函数式异步编程。方法引用示例:Main::fetchPrice 等价于 () -> fetchPrice(),常用在 CompletableFuture.supplyAsync(Main::fetchPrice) 中。六、JDBC 简介各数据库厂商使用统一接口,Java 代码无需针对不同数据库分别开发。Maven 依赖:<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> <scope>runtime</scope> <!-- 运行时才需要此包 --> </dependency>建立连接:DriverManager.getConnection()(自动扫描 classpath 中的驱动)。重要提示:数据库自身就是并发专家,不要用 Java 层锁去干涉数据库操作,除非操作对象是内存数据。
2026年06月02日
11 阅读
0 评论
1 点赞
2026-06-02
Java 反射与动态代理 详细笔记
Java 反射与动态代理 详细笔记一、反射的核心思想反射:在程序运行时,动态获取某个类的完整信息(类名、方法、字段、父类、接口等),并能动态创建对象、调用方法、访问字段。不需要在编译时知道具体类名,可以在运行时根据字符串类名加载类。常用于框架(Spring、MyBatis)、通用工具(IDE 自动补全)、动态代理等。二、Class 类:一切反射的入口2.1 每个类都有唯一的 Class 对象JVM 在第一次加载一个类时,会在内存中为该类创建一个 Class 实例(俗称“说明书”)。这个 Class 实例保存了该类的全部信息(类名、包、方法、字段、父类、接口等)。无论通过哪种方式获取,同一个类的 Class 实例是同一个对象(可用 == 比较)。2.2 获取 Class 实例的三种方法// 方法1:通过类名.class(最直接,编译时已知类) Class<String> cls1 = String.class; // 方法2:通过实例的 getClass() 方法 String s = "hello"; Class<?> cls2 = s.getClass(); // 实际返回 String.class // 方法3:通过 Class.forName(完整类名)(运行时根据字符串加载) Class<?> cls3 = Class.forName("java.lang.String");三者获得的是同一个 Class 对象:cls1 == cls2 == cls3三、通过反射访问字段(Field)3.1 获取字段的方法(返回值是 java.lang.reflect.Field)方法说明能否得到父类字段getField(String name)获取 public 字段(包括父类)✅ 能getDeclaredField(String name)获取当前类声明的任意字段(不限访问修饰符)❌ 不能,只看当前类getFields()获取所有 public 字段(包括父类)✅ 能getDeclaredFields()获取当前类声明的所有字段(不包括父类)❌ 不能3.2 访问私有字段通过 getDeclaredField 拿到私有 Field 对象后,调用 setAccessible(true) 打破封装。然后可以 get(obj) / set(obj, value) 读写该私有字段。class Person { private String name; } Class<Person> cls = Person.class; Field field = cls.getDeclaredField("name"); field.setAccessible(true); // 允许访问私有字段 Person p = new Person(); field.set(p, "Alice"); String name = (String) field.get(p); // "Alice"四、通过反射调用方法(Method)4.1 获取方法的方法(返回值是 java.lang.reflect.Method)方法说明包括父类getMethod(String name, Class<?>... parameterTypes)获取某个 public 方法✅ 包括getDeclaredMethod(...)获取当前类声明的某个方法(不限修饰符)❌ 不包括getMethods()获取所有 public 方法✅ 包括getDeclaredMethods()获取当前类声明的所有方法❌ 不包括4.2 调用方法拿到 Method 对象后,调用 invoke(Object obj, Object... args)。如果方法是 static,第一个参数传 null。// 示例:调用 String 的 length() 方法 String str = "hello"; Method m = String.class.getMethod("length"); int len = (int) m.invoke(str); // len = 5五、通过反射调用构造器(Constructor)5.1 获取构造器方法说明getConstructor(Class<?>... parameterTypes)获取某个 public 构造器getDeclaredConstructor(...)获取任意修饰符的构造器(包括 private)getConstructors() / getDeclaredConstructors()获取所有构造器5.2 创建实例通过 Constructor.newInstance(Object... initargs) 创建对象。比 Class.newInstance() 更灵活(可以传参数,可以调用非 public 构造器)。// 调用带参数的构造器 Constructor<String> cons = String.class.getConstructor(String.class); String s = cons.newInstance("abc"); // 相当于 new String("abc") // 调用私有构造器(单例模式等) Constructor<MyClass> cons2 = MyClass.class.getDeclaredConstructor(); cons2.setAccessible(true); MyClass obj = cons2.newInstance();注意:Class.newInstance() 已过时(只能调用无参 public 构造器),推荐使用 Constructor.newInstance()。六、获取继承关系方法作用示例Class.getSuperclass()返回父类的 Class 对象Integer.class.getSuperclass() → Number.classClass.getInterfaces()返回当前类直接实现的接口数组Integer.class.getInterfaces() → Comparable, Constable, ConstantDescClass.isAssignableFrom(Class<?> cls)判断当前类是否可接收 cls 类型的对象(即 cls 能否向上转型为当前类)Number.class.isAssignableFrom(Integer.class) → true6.1 关于接口的特殊说明对接口调用 getSuperclass() → 返回 null。获取接口的父接口必须用 getInterfaces()。接口的 isAssignableFrom 用法与类一致:Serializable.class.isAssignableFrom(Integer.class) → true。6.2 instanceof 与 isAssignableFrom 的区别instanceof 需要对象实例:obj instanceof NumberisAssignableFrom 需要Class 对象:Number.class.isAssignableFrom(Integer.class)七、动态代理(Dynamic Proxy)7.1 解决的问题不手动编写接口的实现类,就能在运行时动态创建实现了该接口的对象。典型应用:AOP(面向切面编程)、RPC 框架、日志/事务拦截等。7.2 限制条件只能代理接口(不能代理普通类)。如果需要代理普通类,可使用 CGLIB 或 ByteBuddy。7.3 核心 APIjava.lang.reflect.Proxyjava.lang.reflect.InvocationHandler(函数式接口)7.4 实现步骤定义接口(必须,告知代理对象有哪些方法)。实现 InvocationHandler:重写 invoke 方法,定义当接口方法被调用时实际执行的代码。调用 Proxy.newProxyInstance 创建代理实例。将代理实例转型为接口类型,然后正常调用方法。7.5 示例代码(完整)// 1. 接口 public interface Hello { void morning(String name); } // 2. 使用动态代理(不写 HelloWorld 类) import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynamicProxyDemo { public static void main(String[] args) { // 2.1 定义调用处理器 InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("morning")) { String name = (String) args[0]; System.out.println("Good morning, " + name); } return null; // 方法返回 void,所以返回 null } }; // 2.2 创建代理对象 Hello hello = (Hello) Proxy.newProxyInstance( Hello.class.getClassLoader(), // 类加载器 new Class[]{Hello.class}, // 要实现的接口列表 handler // 调用处理器 ); // 2.3 调用方法(实际执行 handler.invoke) hello.morning("Bob"); // 输出:Good morning, Bob } }7.6 动态代理内部原理Proxy.newProxyInstance 会在运行时动态生成一个新的类(例如 $Proxy0)。这个类实现了指定的接口(Hello),并且每个方法的实现都是:public void morning(String name) { super.h.invoke(this, method_morning, new Object[]{name}); }super.h 就是你传入的 InvocationHandler。然后通过 ClassLoader 加载这个类,并实例化返回。换句话说:动态代理 = JVM 自动帮你写了一个实现类,并用反射调用你的 handler。7.7 动态代理与反射的关系动态代理生成的新类($Proxy0)本身也是一个类,JVM 会为它创建 Class 对象。你可以对这个 Class 对象进行任何反射操作。InvocationHandler.invoke 方法中收到的 Method 参数就是通过反射得到的。所以动态代理是反射机制的一个典型应用,而不是替代品。八、总结:什么时候用什么技术?场景推荐技术已知类名,需要调用它的方法/访问字段直接调用,无需反射类名字符串在运行时才确定,需要创建实例Class.forName() + Constructor.newInstance()需要遍历一个对象的所有字段/方法(例如写序列化工具)getDeclaredFields() / getDeclaredMethods()不写实现类,仅通过接口定义行为动态代理需要在方法调用前后统一添加逻辑(日志、事务)动态代理 + InvocationHandler最后记住一句话:反射让你在运行时“看见”并“操作”一个类;动态代理让你在运行时“凭空造出”一个实现了接口的类,并把所有方法调用委托给你写的处理器。
2026年06月02日
10 阅读
0 评论
1 点赞
2026-04-10
JUnit 5 单元测试完整笔记
JUnit 5 单元测试完整笔记一、JUnit 5 概述JUnit 5 是 Java 生态中最主流的单元测试框架,提供丰富的注解、断言和扩展机制。1.1 框架构成(三个子项目)子项目说明JUnit Platform在 JVM 上启动测试框架的基础平台,定义了 TestEngine APIJUnit Jupiter包含 JUnit 5 全新的编程模型和扩展机制JUnit Vintage提供对 JUnit 3 和 JUnit 4 测试的兼容运行支持二、核心测试注解JUnit 5 通过注解控制测试执行流程与行为。注解说明使用场景@Test标记一个方法为测试方法基本单元测试@BeforeEach在每个测试方法执行前运行测试数据准备、对象初始化@AfterEach在每个测试方法执行后运行资源清理、状态重置@BeforeAll在当前测试类的所有测试方法执行前运行一次(需为 static)类级别初始化(如建立数据库连接)@AfterAll在所有测试方法执行后运行一次(需为 static)类级别资源释放@DisplayName为测试类或方法自定义易读的显示名称提升测试报告的可读性@Disabled临时禁用某个测试方法或类跳过尚未实现或需要暂时忽略的测试@ParameterizedTest声明参数化测试方法使用多组数据驱动同一测试逻辑@RepeatedTest重复执行指定次数的测试验证方法稳定性或简单压力测试@Timeout指定测试方法执行超时时间性能验证、防止死循环三、断言方法(Assertions)JUnit 5 提供多种断言来验证测试结果。方法用途assertEquals(expected, actual)断言期望值与实际值相等assertTrue(condition) / assertFalse(condition)断言条件为真 / 假assertThrows(ExceptionType.class, executable)断言执行代码会抛出指定异常assertAll(executables...)组合多个断言,所有断言都会执行再报告失败(避免因一个失败而中断后续验证)四、参数化测试数据源通过 @ParameterizedTest 结合数据源注解,用多组数据测试同一逻辑。注解说明@ValueSource提供简单值数组(如 ints = {1, 2, 3})@CsvSource提供 CSV 格式的字符串数据,支持多列参数@MethodSource引用一个返回 Stream / Iterable 的静态方法提供测试数据@EnumSource使用枚举类的所有值或指定子集作为测试数据五、测试编写原则与最佳实践5.1 好的测试实践(推荐)✅ 示例代码@Test @DisplayName("当输入两个正数时应该返回正确的和") void shouldReturnSumWhenAddingTwoPositiveNumbers() { // Arrange - 准备测试数据 Calculator calculator = new Calculator(); // Act - 执行被测试的方法 int result = calculator.add(2, 3); // Assert - 验证结果 assertEquals(5, result); }✅ 核心原则AAA 模式:Arrange(准备) → Act(执行) → Assert(断言)命名清晰:测试方法名应具有描述性,表达“测什么”和“预期结果”使用 @DisplayName:进一步提高测试报告可读性单一职责:每个测试方法只验证一个功能点覆盖边界条件与异常:不仅要测试正常路径,还要测试空值、零值、负数、越界等异常场景5.2 应该避免的实践(反模式)❌ 反面示例@Test void test1() { // 测试多个不相关的功能 assertEquals(5, calculator.add(2, 3)); assertEquals(6, calculator.multiply(2, 3)); assertEquals("hello", stringUtils.reverse("olleh")); }❌ 错误做法测试方法名无意义(如 test1、testAdd)一个测试方法中验证多个不相关的功能点测试之间存在执行顺序或数据依赖忽略边界条件与异常分支测试代码逻辑过于复杂或包含大量重复代码六、常用测试类型类型说明基本功能测试验证方法在正常输入下的预期行为边界值测试测试输入为 null、空字符串、0、最大值、最小值等边界情况异常测试使用 assertThrows 验证错误输入时是否正确抛出异常参数化测试使用多组数据驱动同一测试逻辑,减少重复代码性能测试结合 @Timeout 或性能测试框架验证执行效率七、测试编写注意事项测试独立性:每个测试应能单独运行,不依赖其他测试的执行结果或执行顺序。数据自包含:测试数据应在测试方法内部准备,避免使用共享可变全局状态。断言消息:为关键断言添加描述性消息,便于快速定位失败原因。资源清理:在 @AfterEach 或 @AfterAll 中释放资源(如文件流、数据库连接)。代码简洁:保持测试代码易读、易维护,必要时可提取私有辅助方法。八、测试覆盖率目标指标建议值整体代码覆盖率≥ 80%核心业务逻辑重点覆盖,尽量达到 90% 以上公共方法所有 public 方法均应有对应测试异常分支与边界条件必须包含相关测试用例覆盖率工具使用 JaCoCo 等工具生成报告并持续监控笔记总结完毕。 按照以上结构和内容复习,即可系统掌握 JUnit 5 的使用方法与单元测试最佳实践。
2026年04月10日
10 阅读
0 评论
0 点赞
2026-04-09
并发编程基础概念
一、并发编程基础概念1.1 并发与并行并发:多任务交替执行(单核/多核),依赖 CPU 时间片轮转。宏观上是同时进行的,微观上是串行。并行:多任务同时执行(必须多核),利用多个 CPU 核心同时处理不同任务。1.2 Java 内存模型 (JMM)主内存:所有线程共享,存储实例变量、静态变量。工作内存:每个线程私有,存储主内存变量的副本。线程不能直接操作主内存,必须先读入工作内存,操作完成后再写回。JMM 定义的 8 种内存交互操作(原子指令):lock(锁定):作用于主内存,将变量标识为线程独占。unlock(解锁):作用于主内存,释放独占状态。read(读取):作用于主内存,将变量值传输到工作内存。load(载入):作用于工作内存,将 read 的值放入变量副本。use(使用):作用于工作内存,将变量值传递给执行引擎。assign(赋值):作用于工作内存,将执行引擎的值赋给变量副本。store(存储):作用于工作内存,将变量值传送到主内存。write(写入):作用于主内存,将 store 的值写入主内存变量。1.3 线程安全问题的根源与表现根源:主内存与工作内存数据不一致。竞态条件:结果依赖线程执行的相对时序(如 check-then-act)。数据竞争:多线程访问同一内存,至少一个写操作,且无同步。死锁:两个及以上线程互相持有对方需要的锁,永久阻塞。二、并发编程三大特性(详细版)特性详细描述实现手段可见性线程 A 修改变量后,线程 B 能立即看到变化。volatile、synchronized、Lock(锁释放前刷新主存)、final原子性一个或多个操作要么全执行,要么全不执行,中间不被调度中断。synchronized、Lock、java.util.concurrent.atomic 原子类有序性避免指令重排序带来的逻辑错误。单线程内语义表现为串行。volatile(内存屏障)、synchronized(锁释放与获取原则)三、线程基础 API 详解3.1 创建线程的方式(含代码细节)方式一:继承 Thread 类public class ThreadExample extends Thread { @Override public void run() { System.out.println("Thread is running"); } public static void main(String[] args) { ThreadExample t = new ThreadExample(); t.start(); } }方式二:实现 Runnable 接口public class RunnableExample implements Runnable { @Override public void run() { System.out.println("Runnable task is running"); } public static void main(String[] args) { Thread t = new Thread(new RunnableExample()); t.start(); // Lambda 简化写法 new Thread(() -> System.out.println("Lambda style")).start(); } }方式三:实现 Callable 接口(有返回值、可抛异常)import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class CallableExample implements Callable<String> { @Override public String call() throws Exception { Thread.sleep(1000); return "Callable result"; } public static void main(String[] args) throws Exception { FutureTask<String> futureTask = new FutureTask<>(new CallableExample()); new Thread(futureTask).start(); // 阻塞获取结果 System.out.println(futureTask.get()); } }3.2 Thread 类构造方法与核心控制构造方法细节:Thread():默认线程名 "Thread-" + n。Thread(String name):指定线程名。Thread(Runnable target):指定任务。Thread(Runnable target, String name):指定任务与名称。线程优先级与守护线程:Thread t = new Thread(); t.setPriority(Thread.MAX_PRIORITY); // 10(最高) t.setPriority(Thread.NORM_PRIORITY); // 5(默认) t.setPriority(Thread.MIN_PRIORITY); // 1(最低) // 守护线程:JVM 退出时不管守护线程是否结束 Thread daemonT = new Thread(() -> { while (true) { /* 后台任务,如日志清理 */ } }); daemonT.setDaemon(true); // 必须在 start() 前设置 daemonT.start();3.3 线程中断机制(方法签名详解)void interrupt():设置中断标志位为 true。boolean isInterrupted():检查标志位,不清除。static boolean interrupted():检查当前线程标志位,并清除(重置为 false)。Thread t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { // 业务逻辑 try { Thread.sleep(1000); } catch (InterruptedException e) { // 捕获异常后标志位会被清除,需手动再次中断以退出循环 Thread.currentThread().interrupt(); } } });四、synchronized 锁机制深度解析4.1 锁的三种形态(代码示例)对象锁(锁当前实例 this):class SyncDemo { // 同步实例方法 public synchronized void method1() { /* 锁 this */ } // 同步代码块 public void method2() { synchronized (this) { /* 锁 this */ } } }类锁(锁 Class 对象):class SyncDemo { // 同步静态方法 public static synchronized void staticMethod1() { /* 锁 SyncDemo.class */ } public void staticMethod2() { synchronized (SyncDemo.class) { /* 锁类对象 */ } } }4.2 锁升级机制(Java 6+,不可逆)偏向锁:无竞争时,在对象头记录线程 ID,线程再次进入仅需检查 ID,无需 CAS。轻量级锁:交替执行时,通过 CAS 自旋尝试获取锁,不阻塞线程(消耗 CPU)。重量级锁:激烈竞争时,自旋失败升级为 OS 互斥量,线程挂起进入阻塞队列(开销大)。五、volatile 与内存屏障5.1 volatile 特性总结保证可见性:写 volatile 变量时强制刷新工作内存到主内存;读 volatile 变量时强制从主内存加载。禁止重排序:通过插入内存屏障防止 CPU/编译器对指令进行重排。不保证原子性:i++ 操作(读-改-写)依然不安全。5.2 内存屏障分类(JMM 规范)屏障类型作用示例场景LoadLoad屏障前读操作完成,才能执行屏障后读操作。读变量A; LoadLoad; 读变量B;LoadStore屏障前读操作完成,才能执行屏障后写操作。读变量A; LoadStore; 写变量B;StoreStore屏障前写操作完成并可见,才能执行屏障后写操作。写变量A; StoreStore; 写变量B;StoreLoad屏障前写操作完成并可见,才能执行屏障后读操作。开销最大,兼具其他三者功能。写变量A; StoreLoad; 读变量B;volatile 屏障插入策略:volatile 写:前插 StoreStore,后插 StoreLoad。volatile 读:后插 LoadLoad + LoadStore。六、Lock 接口与 ReentrantLock 详细 API6.1 Lock 接口方法清单方法详细描述void lock()阻塞获取锁,不可中断,死等。void lockInterruptibly()可响应中断的获取锁,等待时若线程被中断则抛 InterruptedException。boolean tryLock()非阻塞,获取到返回 true,否则立即返回 false。boolean tryLock(long time, TimeUnit unit)超时等待,超时返回 false;等待时可响应中断。void unlock()释放锁。必须在 finally 块中调用。Condition newCondition()返回绑定此锁的条件变量。6.2 ReentrantLock 使用模板class X { private final ReentrantLock lock = new ReentrantLock(); public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock(); // 关键:防止死锁 } } }6.3 公平锁与非公平锁构造// 非公平锁(默认,吞吐量高) ReentrantLock unfairLock = new ReentrantLock(); ReentrantLock unfairLock2 = new ReentrantLock(false); // 公平锁(FIFO,避免线程饥饿但上下文切换多) ReentrantLock fairLock = new ReentrantLock(true);6.4 可中断与超时示例// 可中断锁示例 try { lock.lockInterruptibly(); // 临界区 } catch (InterruptedException e) { // 线程被中断,退出等待队列 } finally { lock.unlock(); } // 超时锁示例 if (lock.tryLock(2, TimeUnit.SECONDS)) { try { // 获取成功 } finally { lock.unlock(); } } else { // 执行备选方案 }七、读写锁与 StampedLock7.1 ReentrantReadWriteLock 特性读写互斥:读锁与写锁互斥。锁降级:持有写锁的线程可以获取读锁,然后释放写锁,降级为读锁。不支持锁升级:持有读锁时直接获取写锁会导致死锁,JVM 禁止此行为。ReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock readLock = rwLock.readLock(); Lock writeLock = rwLock.writeLock(); // 缓存更新场景:锁降级示例 writeLock.lock(); try { // 1. 更新数据库/缓存 readLock.lock(); // 在释放写锁前获取读锁 } finally { writeLock.unlock(); // 降级为读锁 } try { // 2. 后续读取操作(保证数据可见性) } finally { readLock.unlock(); }7.2 StampedLock 三种模式(Java 8+,不可重入)写锁:long stamp = stampedLock.writeLock(); 返回邮戳。悲观读锁:long stamp = stampedLock.readLock();乐观读:无锁机制,依赖版本校验。class Point { private double x, y; private final StampedLock sl = new StampedLock(); // 乐观读(读多写少性能最优) double distanceFromOrigin() { long stamp = sl.tryOptimisticRead(); // 获取乐观读邮戳 double currentX = x, currentY = y; // 无锁读取 if (!sl.validate(stamp)) { // 校验邮戳是否失效 stamp = sl.readLock(); // 失效则升级为悲观读 try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } }八、Condition 条件变量(替换 Object Monitor)8.1 核心方法对比Condition 方法Object 对应方法说明await()wait()释放锁并等待。必须在 while 循环中调用以防虚假唤醒。signal()notify()唤醒一个等待线程。signalAll()notifyAll()唤醒所有等待线程。awaitUninterruptibly()无不可中断等待。awaitUntil(Date deadline)wait(long timeout)带截止时间等待。8.2 生产者-消费者模型(多条件队列)class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); // 防御性编程:必须用 while 循环 public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) // 防止虚假唤醒 notFull.await(); // ... 入队逻辑 notEmpty.signal(); } finally { lock.unlock(); } } }九、原子类与 CAS 底层原理9.1 AtomicInteger 常用方法方法签名操作描述int get()获取当前值。void set(int newValue)设置新值(无原子性保证,仅可见性)。int getAndSet(int newValue)设置新值,返回旧值。boolean compareAndSet(expect, update)CAS 核心操作。int getAndIncrement()等价于 i++。int incrementAndGet()等价于 ++i。int getAndAdd(int delta)加 delta 并返回旧值。9.2 ABA 问题与解决方案ABA 问题描述:线程 1 读取值 A,线程 2 改为 B,线程 3 又改回 A。线程 1 使用 CAS 检查时发现值仍为 A,误以为未变化而更新成功。解决方案:AtomicStampedReference 引入版本号。// 泛型类:AtomicStampedReference<V> AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0); // 初始值100,版本号0 int stamp = ref.getStamp(); int oldValue = ref.getReference(); // 更新时需同时匹配值和时间戳 boolean success = ref.compareAndSet(oldValue, 200, stamp, stamp + 1);
2026年04月09日
15 阅读
0 评论
0 点赞
2026-04-09
Java 多线程与线程池完整笔记
Java 多线程与线程池完整笔记一、线程池基础1.1 什么是线程池线程池是一种多线程处理模式,它在系统启动时预先创建一定数量的线程放入池中。这些线程在没有任务时处于等待状态,当有任务提交时,池中某个空闲线程会被唤醒执行任务。任务执行完成后,线程并不会被销毁,而是返回池中继续等待下一个任务。┌─────────────────────────────────────────────────┐ │ 线程池架构 │ ├─────────────────────────────────────────────────┤ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │线程1│ │线程2│ │线程3│ │线程N│ ← 常驻线程 │ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ │ │ │ │ └─────────┴────┬────┴─────────┘ │ │ ▼ │ │ ┌───────────────┐ │ │ │ 任务队列 │ ← 缓冲待处理任务 │ │ └───────────────┘ │ └─────────────────────────────────────────────────┘1.2 为什么需要线程池优势说明资源复用避免频繁创建和销毁线程的开销,线程对象可重复使用响应速度任务到达时无需等待线程创建,立即执行可管理性统一分配、调优和监控线程,防止资源耗尽流量控制通过队列和最大线程数限制并发量,保护系统二、ThreadPoolExecutor 核心参数详解2.1 七个核心参数public ThreadPoolExecutor( int corePoolSize, // 1. 核心线程数 int maximumPoolSize, // 2. 最大线程数 long keepAliveTime, // 3. 空闲线程存活时间 TimeUnit unit, // 4. 时间单位 BlockingQueue<Runnable> workQueue, // 5. 工作队列 ThreadFactory threadFactory, // 6. 线程工厂 RejectedExecutionHandler handler // 7. 拒绝策略 )参数含义比喻corePoolSize核心线程数,即使空闲也保留在池中的线程数量餐厅正式员工,永不辞退maximumPoolSize最大线程数 = 核心线程 + 临时线程正式员工 + 临时工的上限keepAliveTime临时线程空闲超过该时间则被回收临时工多久没活干就辞退unitkeepAliveTime 的时间单位秒、毫秒等workQueue存放待执行任务的阻塞队列餐厅等位区threadFactory创建线程的工厂,可自定义线程名、优先级等员工招聘标准handler队列满且线程达最大值时的拒绝策略客满时的处理方式2.2 常用工作队列类型队列类型特点适用场景SynchronousQueue不存储任务,直接交给线程执行CachedThreadPoolLinkedBlockingQueue无界队列(默认 Integer.MAX_VALUE)FixedThreadPoolArrayBlockingQueue有界队列,需指定容量需限制队列长度的场景PriorityBlockingQueue优先级队列任务有优先级要求三、线程池执行流程(五步核心逻辑)// 伪代码表示执行逻辑 public void execute(Runnable task) { // 步骤1:当前线程数 < corePoolSize if (workerCount < corePoolSize) { addWorker(task, true); // 新建核心线程执行 return; } // 步骤2:核心线程已满,尝试放入队列 if (workQueue.offer(task)) { // 放入成功,等待空闲线程取走执行 return; } // 步骤3:队列已满,尝试创建临时线程 if (workerCount < maximumPoolSize) { addWorker(task, false); // 新建临时线程执行 return; } // 步骤4:线程数已达最大值,执行拒绝策略 reject(task); }流程图:提交任务 │ ▼ 当前线程数 < corePoolSize? ──Yes──▶ 创建核心线程执行 │ No │ ▼ 任务能否入队? ──Yes──▶ 入队等待执行 │ No(队列已满) │ ▼ 当前线程数 < maximumPoolSize? ──Yes──▶ 创建临时线程执行 │ No │ ▼ 执行拒绝策略四、四种拒绝策略策略类行为比喻使用建议AbortPolicy抛出 RejectedExecutionException直接赶走客人并吵架默认策略,开发常用,快速失败CallerRunsPolicy由提交任务的线程自己执行老板亲自服务客人可减缓提交速度,重要任务兜底DiscardPolicy静默丢弃新任务悄悄赶走客人不重要的任务,如日志上报DiscardOldestPolicy丢弃队列中最老的任务赶走排队最久的人优先处理新任务// 自定义拒绝策略示例 new ThreadPoolExecutor.AbortPolicy() // 抛异常 new ThreadPoolExecutor.CallerRunsPolicy() // 调用者执行 new ThreadPoolExecutor.DiscardPolicy() // 直接丢弃 new ThreadPoolExecutor.DiscardOldestPolicy() // 丢弃最旧的五、Executors 四种快捷线程池⚠️ 开发规范:强制禁止使用 Executors 创建线程池,必须通过 ThreadPoolExecutor 构造函数显式指定参数。原因:FixedThreadPool 和 SingleThreadPool 使用无界队列,可能 OOMCachedThreadPool 和 ScheduledThreadPool 允许创建无限数量线程,可能 OOM工厂方法核心特点适用场景风险newFixedThreadPool(n)固定线程数,无界队列重负载服务器队列无限堆积newCachedThreadPool()0核心,无限临时线程,同步队列大量短期异步任务线程数爆炸newSingleThreadExecutor()单线程,无界队列任务需顺序执行队列无限堆积newScheduledThreadPool(n)支持定时/周期任务定时调度线程数爆炸// ❌ 错误示范 ExecutorService pool = Executors.newFixedThreadPool(10); // ✅ 正确示范 ExecutorService pool = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("my-pool-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy() );六、线程数设置公式6.1 CPU 密集型任务定义:大量时间用于计算,很少等待 I/O线程数 = CPU 核心数 + 1加 1 是为了在某个线程因缺页中断等原因暂停时,有备用线程顶上,最大化 CPU 利用率。6.2 I/O 密集型任务定义:大量时间等待 I/O 操作(网络、磁盘、数据库)线程数 = CPU 核心数 × 2更精确的公式:线程数 = CPU 核心数 × (1 + 平均等待时间 / 平均计算时间)6.3 混合型任务将任务拆分为 CPU 密集和 I/O 密集两部分,分别用不同线程池处理。七、线程池生命周期与优雅关闭7.1 五种状态状态说明接收新任务执行队列任务RUNNING正常运行✅✅SHUTDOWN关闭中(调用 shutdown())❌✅STOP强制停止(调用 shutdownNow())❌❌TIDYING过渡状态,所有任务终止❌❌TERMINATED完全终止❌❌7.2 优雅关闭代码模板public void shutdownGracefully(ExecutorService pool) { pool.shutdown(); // ① 停止接收新任务 try { // ② 等待现有任务完成,最多等待 60 秒 if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); // ③ 超时则强制关闭 // ④ 再等待 5 秒,确认是否终止 if (!pool.awaitTermination(5, TimeUnit.SECONDS)) { System.err.println("线程池未能正常终止"); } } } catch (InterruptedException e) { pool.shutdownNow(); // ⑤ 当前线程被中断,也强制关闭 Thread.currentThread().interrupt(); } }八、ScheduledThreadPoolExecutor 定时任务线程池8.1 概述ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor,专门用于执行定时和周期性任务,是传统 Timer 的替代品。对比项TimerScheduledThreadPoolExecutor线程模型单线程多线程池异常处理任务抛异常则 Timer 线程终止单任务异常不影响其他任务任务调度基于绝对时间基于相对时间,更精准适用场景简单定时生产级定时调度8.2 三种核心 APIScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5); // 1. 延迟执行一次 ScheduledFuture<?> future = scheduler.schedule( () -> System.out.println("延迟执行"), 5, TimeUnit.SECONDS ); // 2. 固定频率执行(不管上次是否完成) scheduler.scheduleAtFixedRate( () -> System.out.println("固定频率"), 1, // 首次延迟 1 秒 3, // 每 3 秒执行一次 TimeUnit.SECONDS ); // 3. 固定延迟执行(等上次完成后再延迟) scheduler.scheduleWithFixedDelay( () -> System.out.println("固定延迟"), 1, // 首次延迟 1 秒 3, // 上次完成后等待 3 秒 TimeUnit.SECONDS );8.3 scheduleAtFixedRate vs scheduleWithFixedDelay方法调度方式效果scheduleAtFixedRate固定频率(周期固定)不管任务执行多久,严格按周期触发,可能任务堆叠scheduleWithFixedDelay固定延迟(间隔固定)等上次执行完,再等固定时间才触发,周期 = 执行时间 + 延迟scheduleAtFixedRate (周期 = 3秒) 任务执行2秒 → 等待1秒 → 下一轮 任务执行4秒 → 立即触发下一轮(无等待) scheduleWithFixedDelay (延迟 = 3秒) 任务执行2秒 → 等待3秒 → 下一轮(间隔5秒) 任务执行4秒 → 等待3秒 → 下一轮(间隔7秒)九、线程池性能监控与调优9.1 关键监控指标指标含义告警阈值排查方向活跃线程数当前正在工作的线程数> 最大线程数 80%线程数不足或任务耗时过长队列长度等待执行的任务数> 队列容量 70%消费速度跟不上生产速度任务完成数已完成任务累计增长停滞线程池可能卡死或阻塞拒绝任务数被拒绝的任务累计> 0(正常应为 0)线程数和队列容量需调大线程池大小当前池中线程总数长期接近 maximumPoolSize调大 maximumPoolSize任务等待时间任务从提交到执行的时间> 业务可接受阈值处理能力不足9.2 监控实现方案@Component public class ThreadPoolMonitor { private final ThreadPoolExecutor executor; @Scheduled(fixedDelay = 30000) // 每30秒采集一次 public void monitor() { int activeCount = executor.getActiveCount(); long completedCount = executor.getCompletedTaskCount(); int queueSize = executor.getQueue().size(); int poolSize = executor.getPoolSize(); long rejectCount = // 自定义记录 log.info("线程池监控 - 活跃:{}, 队列:{}, 池大小:{}, 已完成:{}, 拒绝:{}", activeCount, queueSize, poolSize, completedCount, rejectCount); // 告警判断 if (queueSize > queueCapacity * 0.7) { alert("队列使用率超过70%"); } } }9.3 调优策略现象可能原因解决方案队列持续增长消费速度 < 生产速度增加线程数,或优化任务执行效率活跃线程数持续满任务耗时过长拆分任务、异步化、增加最大线程数拒绝次数频繁线程池容量不足增大队列容量和最大线程数CPU 使用率低但队列满线程阻塞于 I/O增加线程数(I/O 密集型公式)OOM 内存溢出无界队列堆积改用有界队列 + 合理拒绝策略十、最佳实践总结10.1 编码规范// ✅ 正确:显式创建 ThreadPoolExecutor ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, // 核心线程数 20, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲超时 new ArrayBlockingQueue<>(1000), // 有界队列 new NamedThreadFactory("biz-pool"), // 自定义线程工厂 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); // ❌ 错误:使用 Executors 快捷方法 ExecutorService pool = Executors.newFixedThreadPool(10);10.2 必须设置的内容[ ] 有界队列(避免 OOM)[ ] 合适的拒绝策略(根据业务选型)[ ] 有意义的线程名称(便于排查问题)[ ] 监控指标采集(JMX / Micrometer)[ ] 优雅关闭逻辑(应用停止时调用)10.3 参数选择参考业务类型corePoolSizemaximumPoolSize队列类型队列容量计算密集型 APICPU 核数 + 1CPU 核数 + 1ArrayBlockingQueue100~500I/O 密集型 APICPU 核数 × 2CPU 核数 × 4ArrayBlockingQueue500~2000后台批量处理1~51~10LinkedBlockingQueue10000+即时响应任务10~5050~200SynchronousQueue0本笔记涵盖了 Java 多线程线程池的核心知识点,建议结合实战加深理解。
2026年04月09日
7 阅读
0 评论
1 点赞
1
2
3
4