以下代码的输出是什么?解释原因
String s1 = new String("Hello");
String s2 = "Hello";
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
s1 == s2 输出 false
== 比较的是对象的内存地址。
String s1 = new String(“Hello”); 会在堆内存中创建一个新的 String 对象,无论字符串常量池中是否已有 “Hello”。
String s2 = “Hello”; 会直接使用字符串常量池中的 “Hello”(如果已存在则复用,否则新建并放入池中)。
因此 s1 和 s2 指向不同的内存地址,== 比较结果为 false。
s1.equals(s2) 输出 true
equals() 比较的是字符串的内容。
s1 和 s2 的内容都是 “Hello”,字符序列完全相同。
String 类重写了 equals() 方法,会逐字符比较,因此返回 true。
为什么Java的Object类要设计equals()和hashCode()两个方法?它们之间有什么契约?
现有100万条数据需要频繁按ID查询,且要求按插入顺序遍历,你会选择哪种集合?为什么?
若需线程安全,可用 Collections.synchronizedMap(new LinkedHashMap<>()) 或 ConcurrentLinkedHashMap(第三方库)
线程安全的单例模式实现(双重校验锁 + volatile)
public class Singleton {// 1. volatile 保证可见性和禁止指令重排序private static volatile Singleton instance;// 2. 私有构造方法,防止外部实例化private Singleton() {}// 3. 双重校验锁(Double-Checked Locking)public static Singleton getInstance() {if (instance == null) { // 第一次检查(避免不必要的同步)synchronized (Singleton.class) { // 加锁if (instance == null) { // 第二次检查(确保唯一性)instance = new Singleton(); // 安全初始化}}}return instance;}
}
为什么这是线程安全的?
ThreadLocal是什么?它的典型使用场景和内存泄漏风险是什么?
ThreadLocal 是 Java 提供的线程本地变量机制,允许每个线程拥有独立的变量副本,避免多线程竞争。
核心特点:
线程隔离:每个线程通过 ThreadLocal 访问自己的变量副本,互不干扰。
基于 ThreadLocalMap 实现:数据实际存储在当前线程的 ThreadLocalMap 中(键为 ThreadLocal 实例,值为变量副本)。
典型使用场景
1、线程安全的全局变量
场景:需要跨方法/类传递线程专属数据(如用户会话信息、事务ID)。
示例:Web 框架(如Spring)用 ThreadLocal 存储当前请求的用户信息:
public class UserContext {private static final ThreadLocal<User> currentUser = new ThreadLocal<>();public static void set(User user) {currentUser.set(user);}public static User get() {return currentUser.get();}public static void remove() {currentUser.remove(); // 防止内存泄漏}
}
2、避免参数传递
场景:在调用链深处需要共享某个变量,但不想逐层传参。
示例:数据库连接管理(如Hibernate的 Session 绑定到当前线程)。
3、线程安全的工具类
场景:SimpleDateFormat 非线程安全,可用 ThreadLocal 为每个线程创建独立实例
public class DateUtils {private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));public static String format(Date date) {return formatter.get().format(date);}
}
内存泄漏风险
泄漏原因
ThreadLocalMap 的键是弱引用(WeakReference),但值是强引用:
当 ThreadLocal 实例(键)被回收后,ThreadLocalMap 中会残留 key=null 的条目(值未被回收)。
如果线程长期运行(如线程池复用线程),这些无效条目会累积,导致内存泄漏。
示意
Thread → ThreadLocalMap → Entry(WeakReference key, StrongReference value)↓key=null, value=Object(泄漏!)
如何避免?
主动调用 remove():
使用完 ThreadLocal 后,显式调用 remove() 清理当前线程的条目:
try {threadLocal.set(data);// ... 业务逻辑
} finally {threadLocal.remove(); // 必须清理!
}
使用 try-with-resources 模式(Java 21+):
try (var ignored = threadLocal.withInitial(data)) {// ... 业务逻辑
} // 自动调用 remove()
其他注意事项
线程池场景风险更高:
线程池中的线程会复用,如果不清理 ThreadLocal,可能导致前一个任务的脏数据影响后续任务。
子线程继承问题:
默认子线程无法继承父线程的 ThreadLocal 值,需用 InheritableThreadLocal(但仍有泄漏风险)。