- NPE 空指针异常(Null Pointer Exception)
1)使用注解 @NotNull 和 @Nullable
2)用 Optional 处理链式调用
3)用 Objects.equals(a,b) 代替 a.equals(b),能够避免任意对象为 null 时的 NPE。
4)使用空对象模式,空对像模式通过一个特殊对象代替不存在的情况,代表对象不存在时的默认行为模式。例如:用 Empty List 代替 null,EmptyList 能够正常遍历:Collections.emptyList();
- 线程安全
JVM 的内存模型十分复杂,难以理解, <<Java 并发编程实战>>告诉我们,除非你对 JVM 的线程安全原理十分熟悉,否则应该严格遵守基本的 Java 线程安全规则,使用 Java 内置的线程安全的类及关键字。
熟练使用线程安全类 ConcurrentHashMap
java
public class ConcurrentHashMapExample {
private Map<String, String> map = new ConcurrentHashMap<>();
public void append(String key, String suffix) {
// 使用 computeIfPresent 原子操作
map.computeIfPresent(key, (k, v) -> v + suffix);
}
}
保证变更的原子性
使用不可变对象
**正确性优先于性能:**不要因为担心性能问题而放弃使用 synchronized,volatile 等关键字,或者采用一些非常规写法。
多线程环境中的volatile:在多线程编程中,每个线程可以拥有变量的独立拷贝,这可能导致数据不一致的问题。声明一个变量为volatile可以保证所有线程看到的都是主存中的最新版本,从而在一定程度上解决了线程间可见性的问题。尽管volatile不保证复合操作的原子性,它通过内存屏障来防止指令重排序,确保了操作的顺序性。
Java中的volatile:在Java中,volatile不仅保证了单个变量的可见性,还可以用于实现轻量级的同步,特别是在使用双重检查锁定模式(Double-Checked Locking)设计单例模式时。volatile关键字通过内存屏障来防止指令重排,这对于维持对象引用的可见性和一致性至关重要。
线程池使用不当
java
public class ThreadPoolExample {
// 没有任何限制的线程池, 使用起来很方便, 但当一波请求高峰到达时, 可能会创建大量线程, 导致系统崩溃
private static Executor executor = Executors.newCachedThreadPool();
}
手动创建线程池
java
public class ManualCreateThreadPool {
// 手动创建资源有限的线程池
private Executor executor = new ThreadPoolExecutor(10, 10, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("work-%d").build());
}
通过 AOP 统一异常处理
-
避免未知异常抛给调用方, 将未知异常转为 Result 或者通用异常类型
-
统一异常日志的打印和监控
处理 Checked Exception
Checked Exception 是在编译期要求必须处理的异常,也就是非 RuntimeException 类型的异常,但 Java Checked 的异常给接口的调用者造成了一定的负担,导致异常声明层层传递,如果顶层能够处理该异常,我们可以通过 lombok 的 @SneakyThrows 注解规避 Checked exception。
Try catch 线程逻辑
特殊异常的处理
InterruptedException 一般是上层调度者主动发起的中断信号,例如某个任务执行超时,那么调度者通过将线程置为 interuppted 来中断任务,对于这类异常我们不应该在 catch 之后忽略,应该向上抛出或者将当前线程置为 interuppted。
避免 catch Error
不要吞并 Error,Error 设计本身就是区别于异常,一般不应该被 catch,更不能被吞掉。举个例子,OOM 有可能发生在任意代码位置,如果吞并 Error,让程序继续运行,那么以下代码的 start 和 end 就无法保证一致性。
内存/资源泄漏
虽然 JVM 有垃圾回收机制,但并不意味着内存泄漏问题不存在,一般内存泄漏发生在在长时间持对象无法释放的场景,比如静态集合,内存中的缓存数据,运行时类生成技术等。
Spring 事务问题,注意事务注解失效的场景
当打上 @Transactional 注解的 spring bean 被注入时,spring 会用事务代理过的对象代替原对象注入。
但是如果注解方法被同一个对象中的另一个方法里面调用,则该调用无法被 Spring 干预,自然事务注解也就失效了。
java
@Component
public class TransactionNotWork {
public void doTheThing() {
actuallyDoTheThing();
}
@Transactional
public void actuallyDoTheThing() {
}
}