Java 原生 API
模式总览
本文覆盖以下可迁移模式:
| # |
模式名称 |
一句话口诀 |
覆盖用例 |
| 1 |
字符串常量池复用 |
相同字面量共享同一对象 |
String.intern()、编译期常量折叠 |
| 2 |
契约式设计 |
重写 equals 必须重写 hashCode |
HashMap 键、Set 元素 |
| 3 |
线程状态机协作 |
interrupt 是协作式终止标志 |
Thread.interrupt()、Future.cancel() |
| 4 |
双亲委派加载 |
父类加载器优先防止类冲突 |
ClassLoader、Tomcat 类加载 |
| 5 |
异常链传递 |
包装异常保留原始栈踪迹 |
ServletException、RuntimeException |
| 6 |
不可变视图 |
原始集合的只读包装 |
Collections.unmodifiableList() |
| 7 |
空值防御性编程 |
明确表达可能为空的返回值 |
Optional、Objects.requireNonNull() |
| 8 |
异步组合编程 |
链式调用编排多阶段任务 |
CompletableFuture.thenCompose() |
| 9 |
分段锁到 CAS |
减少锁粒度提升并发度 |
ConcurrentHashMap 演进 |
| 10 |
资源自动管理 |
try-with-resources 保证释放 |
AutoCloseable、Files.lines() |
java.lang
System
这个类看起来是 JavaLangAccess 的实现(虽然没有做过 implements 声明),所以注册钩子也可以用这个方法:
sun.misc.SharedSecrets.getJavaLangAccess().registerShutdownHook
identityHashCode
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { Map map = new HashMap(); map.put(1, 2); map.put(3, 4); System.out.println(map.hashCode()); System.out.println(System.identityHashCode(map));
Object object = new Object(); System.out.println(object.hashCode()); System.out.println(System.identityHashCode(object)); }
|
Map 自己覆盖了 hashCode() 方法,但普通 Object 没有。
exit
1 2 3
| public static void exit(int status) { Runtime.getRuntime().exit(status); }
|
通常这个方法需要传入非 0 值,代表 abnormal 结束。这个方法的调用通常是不会正常返回的,也就是说在下面写什么代码都执行不到了,包括 finally。这产生了一个很经典的陷阱,如果在这样的场景下:
1 2 3 4 5 6 7 8 9
| synchronized(lock) { System.exit(1); }
synchronized(lock) { }
|
因为线程死锁,清理钩子永远执行不完。而 exit 也会卡死在这里,所以是个只能进不能出的虫洞(wormhole)。
改进方法是:
1 2 3 4 5 6
| synchronized(lock) { new Thread(() -> { System.exit(1); }).start(); }
|
Runtime
addShutdownHook
1 2 3 4 5 6 7
| public void addShutdownHook(Thread hook) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("shutdownHooks")); } ApplicationShutdownHooks.add(hook); }
|
exit
initiating its shutdown sequence 初始化关闭流程用。
The virtual machine’s shutdown sequence consists of two phases:
- shutdown hooks are started in some unspecified order and allowed to run concurrently until they finish.
- all uninvoked finalizers are run if finalization-on-exit has been enabled. Once this is done the virtual machine halts.
第一次调用这个方法触发关闭钩子运行的话,第二次调用这个方法则会无限阻塞(下面我们会看到这是怎么实现的);如果此时钩子运行完,而 on-exit finalization 被激活,前一个方法 exit 非 0,则第二次调用 halts,否则无限阻塞(blocks indefinitely)。
Runtime -> Shutdown 来关闭。这是一个包级别的类,主要是为了 Runtime 服务的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| static void exit(int status) { boolean runMoreFinalizers = false; synchronized (lock) { if (status != 0) runFinalizersOnExit = false; switch (state) { case RUNNING: state = HOOKS; break; case HOOKS: break; case FINALIZERS: if (status != 0) { halt(status); } else { runMoreFinalizers = runFinalizersOnExit; } break; } } if (runMoreFinalizers) { runAllFinalizers(); halt(status); } synchronized (Shutdown.class) { beforeHalt(); sequence(); halt(status); } }
private static void sequence() { synchronized (lock) { if (state != HOOKS) return; } runHooks(); boolean rfoe; synchronized (lock) { state = FINALIZERS; rfoe = runFinalizersOnExit; } if (rfoe) runAllFinalizers(); }
private static void runHooks() { for (int i = 0; i < MAX_SYSTEM_HOOKS; i++) { try { Runnable hook; synchronized (lock) { currentRunningHook = i; hook = hooks[i]; } if (hook != null) hook.run(); } catch (Throwable t) { if (t instanceof ThreadDeath) { ThreadDeath td = (ThreadDeath) t; throw td; } } } }
|
Shutdown 维护一个 hooks 数组,每次遍历一个数组求一次锁。
但 Runtime 增加钩子是通过一个来自于 Runnable 的 ApplicationShutdownHooks 的 add,它内部维护了一个 IdentityHashMap<Thread, Thread> hooks。真正执行的 ApplicationShutdownHooks 是一个单例,它在初始化的时候自注册到 Shutdown 的 hooks 数组里,然后执行的时候对每个线程进行按顺序的同步 start。
但:
- 线程钩子的注册时机不一样。
- 线程的 start 的实际执行流程也是未定的。
所以钩子是乱序执行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| static { try { Shutdown.add(1, false, new Runnable() { public void run() { runHooks(); } }); hooks = new IdentityHashMap<>(); } catch (IllegalStateException e) { hooks = null; } }
static void runHooks() { Collection<Thread> threads; synchronized (ApplicationShutdownHooks.class) { threads = hooks.keySet(); hooks = null; }
for (Thread hook : threads) { hook.start(); } for (Thread hook : threads) { while (true) { try { hook.join(); break; } catch (InterruptedException ignored) { } } } }
|
另外,Shutdown.add 的存在,允许从其他入口注册多个钩子。
ApplicationShutdownHooks 还有相应的几个兄弟:DeleteOnExitHook、Console restore hook。这些兄弟在 Open JDK 里是有的,但 Kona JDK 就没有。
String
intern() 机制与字符串常量池
String.intern() 是字符串常量池的核心方法。JLS §3.10.5 定义了字符串字面量的规范:相同字面量的字符串引用指向同一对象。intern() 方法将运行时创建的字符串加入常量池,返回池中的引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class StringInternDemo { public static void main(String[] args) { String s1 = "hello"; String s2 = "hello"; System.out.println(s1 == s2); String s3 = new String("hello"); String s4 = new String("hello"); System.out.println(s3 == s4); System.out.println(s1 == s3); String s5 = s3.intern(); System.out.println(s1 == s5); } }
|
JDK 7 之后,字符串常量池从 PermGen 移至堆中,GC 可以回收无用的字符串。intern() 在大量不同字符串场景下会导致常量池膨胀,需谨慎使用。
String vs StringBuilder vs StringBuffer
String 是不可变对象,每次拼接生成新对象。StringBuilder 是可变字符序列,非线程安全,性能最优。StringBuffer 是 StringBuilder 的线程安全版本,方法加 synchronized。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class StringComparison { public static void main(String[] args) { long start = System.nanoTime(); String result = ""; for (int i = 0; i < 1000; i++) { result += "a"; } System.out.println("String: " + (System.nanoTime() - start) + " ns"); start = System.nanoTime(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append("a"); } System.out.println("StringBuilder: " + (System.nanoTime() - start) + " ns"); start = System.nanoTime(); StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 1000; i++) { buffer.append("a"); } System.out.println("StringBuffer: " + (System.nanoTime() - start) + " ns"); } }
|
Object
equals/hashCode 契约
JLS §17.2 定义了 equals 和 hashCode 的契约:如果两个对象 equals 返回 true,它们的 hashCode 必须相同。违反此契约会导致 HashMap 等基于哈希的集合失效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } }
|
clone 的浅拷贝陷阱
Object.clone() 默认实现是浅拷贝,只复制字段引用而不复制对象。包含可变对象的类需要实现深拷贝,或使用拷贝构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class ShallowCopyTrap implements Cloneable { private int[] data; public ShallowCopyTrap(int[] data) { this.data = data; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public static void main(String[] args) throws CloneNotSupportedException { int[] originalData = {1, 2, 3}; ShallowCopyTrap original = new ShallowCopyTrap(originalData); ShallowCopyTrap cloned = (ShallowCopyTrap) original.clone(); cloned.data[0] = 999; System.out.println(original.data[0]); } }
|
wait/notify 机制
wait/notify 是线程间协作的基础机制,必须在 synchronized 块内调用。JLS §17.1 定义了 wait 释放监视器锁,notify 唤醒等待线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class WaitNotifyDemo { private final Object lock = new Object(); private boolean ready = false; public void producer() throws InterruptedException { synchronized (lock) { ready = true; lock.notify(); } } public void consumer() throws InterruptedException { synchronized (lock) { while (!ready) { lock.wait(); } System.out.println("Consumed"); } } }
|
Thread
线程状态机
JLS §17.1 定义了 6 种线程状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class ThreadStateDemo { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); System.out.println(thread.getState()); thread.start(); System.out.println(thread.getState()); Thread.sleep(100); System.out.println(thread.getState()); thread.join(); System.out.println(thread.getState()); } }
|
interrupt 机制
Thread.interrupt() 设置线程的中断标志位,不会立即终止线程。线程需要定期检查 Thread.interrupted() 或 InterruptedException 来响应中断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class InterruptDemo { public static void main(String[] args) throws InterruptedException { Thread worker = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } System.out.println("Thread interrupted gracefully"); }); worker.start(); Thread.sleep(500); worker.interrupt(); worker.join(); } }
|
ThreadLocal 原理
ThreadLocal 为每个线程提供独立的变量副本。内部使用 ThreadLocalMap 存储,以 ThreadLocal 实例为键,弱引用防止内存泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class ThreadLocalDemo { private static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); public void processDate(String dateStr) { SimpleDateFormat sdf = dateFormat.get(); try { Date date = sdf.parse(dateStr); System.out.println(date); } catch (ParseException e) { throw new RuntimeException(e); } finally { dateFormat.remove(); } } }
|
ClassLoader
双亲委派模型
ClassLoader 采用双亲委派模型:加载类时先委托父加载器,只有父加载器无法加载时才自己尝试。JLS §12.2 定义了类加载器的层次结构。
1 2 3 4 5 6 7 8 9 10 11
| public class ClassLoaderDemo { public static void main(String[] args) { ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); ClassLoader extensionLoader = systemLoader.getParent(); ClassLoader bootstrapLoader = extensionLoader.getParent(); System.out.println("System Loader: " + systemLoader); System.out.println("Extension Loader: " + extensionLoader); System.out.println("Bootstrap Loader: " + bootstrapLoader); } }
|
自定义类加载器场景
自定义类加载器用于热部署、模块隔离、加密类加载等场景。需要重写 findClass 方法,遵守双亲委派或打破委派模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class CustomClassLoader extends ClassLoader { private final String classPath; public CustomClassLoader(String classPath, ClassLoader parent) { super(parent); this.classPath = classPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] classData = loadClassData(name); return defineClass(name, classData, 0, classData.length); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } private byte[] loadClassData(String name) throws IOException { String path = classPath + name.replace('.', '/') + ".class"; try (InputStream in = new FileInputStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream()) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } return out.toByteArray(); } } }
|
Throwable
异常体系设计
Java 异常体系分为 Checked 异常(编译时检查)和 Unchecked 异常(RuntimeException 及其子类)。JLS §11.2 定义了异常的编译时检查规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class ExceptionDemo { public void readFile() throws IOException { try (FileReader reader = new FileReader("file.txt")) { } } public void divide(int a, int b) { if (b == 0) { throw new IllegalArgumentException("Divisor cannot be zero"); } } public void process() { try { readFile(); } catch (IOException e) { throw new RuntimeException("Failed to process file", e); } } }
|
try-with-resources
try-with-resources 自动实现 AutoCloseable 接口的资源释放,JLS §14.20.3.1 定义了资源管理语句的语义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class TryWithResourcesDemo { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("input.txt"); FileOutputStream fos = new FileOutputStream("output.txt")) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); } } catch (IOException e) { e.printStackTrace(); } } }
|
java.util
Collections
工厂方法
Collections 提供多种工厂方法创建不可变、同步、单元素集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class CollectionsDemo { public static void main(String[] args) { List<String> unmodifiable = Collections.unmodifiableList( new ArrayList<>(Arrays.asList("a", "b", "c")) ); List<String> synchronizedList = Collections.synchronizedList( new ArrayList<>() ); List<String> singleton = Collections.singletonList("only"); Set<String> emptySet = Collections.emptySet(); } }
|
Arrays
asList 陷阱
Arrays.asList() 返回固定大小的列表,不支持增删操作。返回的列表是原数组的视图,修改列表会影响数组。
1 2 3 4 5 6 7 8 9 10 11 12
| public class ArraysDemo { public static void main(String[] args) { String[] array = {"a", "b", "c"}; List<String> list = Arrays.asList(array); list.set(0, "x"); System.out.println(array[0]); List<String> modifiable = new ArrayList<>(Arrays.asList(array)); modifiable.add("d"); } }
|
sort 的 TimSort 算法
Arrays.sort() 使用 TimSort 算法,结合归并排序和插入排序,时间复杂度为 O(n log n),对部分有序数据优化。
1 2 3 4 5 6 7 8 9 10 11
| public class ArraysSortDemo { public static void main(String[] args) { int[] array = {5, 2, 8, 1, 9}; Arrays.sort(array); System.out.println(Arrays.toString(array)); String[] names = {"Alice", "Bob", "Charlie"}; Arrays.sort(names, (a, b) -> Integer.compare(a.length(), b.length())); System.out.println(Arrays.toString(names)); } }
|
Optional
正确使用方式
Optional 用于明确表达可能为空的返回值,避免 NullPointerException。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class OptionalDemo { public static void main(String[] args) { Optional<String> optional = Optional.ofNullable(getValue()); String result = optional.orElse("default"); optional.ifPresent(value -> System.out.println(value)); Integer length = optional.map(String::length).orElse(0); } private static String getValue() { return null; } }
|
反模式
不要将 Optional 用作字段类型或方法参数,应仅用于返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class User { private Optional<String> name; }
public class User { private String name; public Optional<String> getName() { return Optional.ofNullable(name); } }
|
Objects
工具方法
Objects 提供空安全的方法:requireNonNull、equals、hash、isNull、nonNull。
1 2 3 4 5 6 7 8 9 10
| public class ObjectsDemo { public static void main(String[] args) { String name = null; String nonNullName = Objects.requireNonNull(name, "Name cannot be null"); boolean equal = Objects.equals(name, "test"); int hash = Objects.hash("field1", "field2"); boolean isNull = Objects.isNull(name); } }
|
java.util.concurrent
CompletableFuture
组合异步操作
CompletableFuture 提供丰富的异步组合 API:thenApply、thenCompose、thenCombine、allOf、anyOf。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class CompletableFutureDemo { public static void main(String[] args) throws Exception { CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return "Hello"; }); CompletableFuture<Integer> result = future .thenApply(String::length) .thenCompose(len -> CompletableFuture.supplyAsync(() -> len * 2)); System.out.println(result.get()); CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "A"); CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "B"); CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2); all.get(); System.out.println(f1.get() + f2.get()); } }
|
ConcurrentHashMap
分段锁到 CAS 的演进
ConcurrentHashMap 在 JDK 7 使用分段锁(Segment),JDK 8 改为 CAS + synchronized,提升并发性能。
1 2 3 4 5 6 7 8 9 10 11 12
| public class ConcurrentHashMapDemo { public static void main(String[] args) { ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("key1", 1); map.computeIfAbsent("key2", k -> 2); map.computeIfPresent("key1", (k, v) -> v + 1); map.merge("key3", 1, Integer::sum); map.forEach((k, v) -> System.out.println(k + "=" + v)); } }
|
ThreadPoolExecutor
核心参数与拒绝策略
ThreadPoolExecutor 核心参数:corePoolSize、maximumPoolSize、keepAliveTime、workQueue、handler。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class ThreadPoolDemo { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.CallerRunsPolicy() ); for (int i = 0; i < 20; i++) { final int taskId = i; executor.execute(() -> { System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName()); }); } executor.shutdown(); } }
|
Path/Files
现代文件操作 API
java.nio.file.Path 和 Files 提供现代文件操作 API,支持路径操作、文件属性、目录遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class NioFilesDemo { public static void main(String[] args) throws IOException { Path path = Paths.get("example.txt"); Files.write(path, "Hello, NIO!".getBytes(StandardCharsets.UTF_8)); List<String> lines = Files.readAllLines(path); Path copy = Files.copy(path, Paths.get("copy.txt")); Files.walk(Paths.get(".")) .filter(p -> p.toString().endsWith(".java")) .forEach(System.out::println); Files.deleteIfExists(copy); } }
|
try-with-resources 与 AutoCloseable
实现 AutoCloseable 接口的资源可以在 try-with-resources 语句中自动关闭。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class AutoCloseableDemo implements AutoCloseable { private final String name; public AutoCloseableDemo(String name) { this.name = name; System.out.println(name + " created"); } public void doSomething() { System.out.println(name + " doing something"); } @Override public void close() { System.out.println(name + " closed"); } public static void main(String[] args) { try (AutoCloseableDemo resource = new AutoCloseableDemo("Resource")) { resource.doSomething(); } } }
|
模式速查表
| 听到的需求关键词 |
对应模式 |
方案 |
口诀 |
| 字符串重复创建 |
字符串常量池复用 |
String.intern() 或编译期常量 |
相同字面量共享同一对象 |
| HashMap 键比较 |
契约式设计 |
重写 equals 和 hashCode |
重写 equals 必须重写 hashCode |
| 线程优雅终止 |
线程状态机协作 |
Thread.interrupt() + 检查标志 |
interrupt 是协作式终止标志 |
| 类隔离加载 |
双亲委派加载 |
自定义 ClassLoader |
父类加载器优先防止类冲突 |
| 异常信息保留 |
异常链传递 |
new RuntimeException(cause) |
包装异常保留原始栈踪迹 |
| 集合只读保护 |
不可变视图 |
Collections.unmodifiableList() |
原始集合的只读包装 |
| 返回值可能为空 |
空值防御性编程 |
Optional.ofNullable() |
明确表达可能为空的返回值 |
| 多阶段异步任务 |
异步组合编程 |
CompletableFuture.thenCompose() |
链式调用编排多阶段任务 |
| 高并发集合 |
分段锁到 CAS |
ConcurrentHashMap |
减少锁粒度提升并发度 |
| 资源自动释放 |
资源自动管理 |
try-with-resources |
try-with-resources 保证释放 |