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:

  1. shutdown hooks are started in some unspecified order and allowed to run concurrently until they finish.
  2. 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。

但:

  1. 线程钩子的注册时机不一样。
  2. 线程的 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); // true

String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s3 == s4); // false
System.out.println(s1 == s3); // false

String s5 = s3.intern();
System.out.println(s1 == s5); // true
}
}

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]); // 999
}
}

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()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE

Thread.sleep(100);
System.out.println(thread.getState()); // TIMED_WAITING

thread.join();
System.out.println(thread.getState()); // TERMINATED
}
}

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); // null
}
}

自定义类加载器场景

自定义类加载器用于热部署、模块隔离、加密类加载等场景。需要重写 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]); // x

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)); // [1, 2, 5, 8, 9]

String[] names = {"Alice", "Bob", "Charlie"};
Arrays.sort(names, (a, b) -> Integer.compare(a.length(), b.length()));
System.out.println(Arrays.toString(names)); // [Bob, Alice, Charlie]
}
}

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()); // 10

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()); // AB
}
}

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();
}
}

java.io / java.nio

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 保证释放