Java懒加载与空检查:移除方法级synchronized后的代码是否可行?
修改后的代码完全不可行,还存在几个致命问题
- 直接触发空指针异常:你把
wordsInFile的初始化代码去掉了,它一开始是null,执行synchronized(wordsInFile)时会直接抛出NullPointerException——Java里锁对象不能是null。就算你没删初始化,同步块里重新赋值wordsInFile = new HashSet<>()后,锁对象会被替换,后续线程的锁会失效,完全起不到同步加载的作用,大概率会出现多线程重复加载资源的情况。 - static方法访问非static成员的基础错误:不管是原始代码还是修改后的代码,
IsFound是static方法,但wordsInFile是非static成员,这在Java里编译都通不过——static方法不能直接访问实例变量,必须先创建类的实例才行。 - 空集合判断的隐患:就算解决了null问题,用
wordsInFile.isEmpty()判断是否需要加载也不靠谱,如果资源加载失败(比如文件找不到),wordsInFile会是空集合,后续每次调用都会重复尝试加载,白白浪费资源。
正确的优化方案(懒加载+减少锁范围)
要实现“只在加载时加锁,查询时无锁”的需求,应该用双重检查锁(DCL),同时保证变量的可见性,还要修正static/非static的匹配问题,示例代码如下:
// volatile保证多线程下的可见性,禁止指令重排 private static volatile Set<String> wordsInFile; // 方法名遵循Java小驼峰规范 private static boolean isFound(String word) { if (word == null) { return false; } // 第一次无锁检查,避免每次调用都进入同步块 if (wordsInFile == null) { // 用类对象当锁,匹配static方法的作用域 synchronized (WebUtils.class) { // 第二次检查,防止多个线程等待锁后重复初始化 if (wordsInFile == null) { // 先初始化临时集合,避免其他线程拿到未加载完成的集合 Set<String> tempWordSet = new HashSet<>(); try (InputStream inputStream = WebUtils.class.getClassLoader().getResourceAsStream("words.txt"); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { String line; while ((line = bufferedReader.readLine()) != null) { tempWordSet.add(line); } // 加载完成后再赋值给全局变量 wordsInFile = tempWordSet; } catch (Exception e) { // 不要空吞异常,至少打日志方便排查 e.printStackTrace(); // 加载失败可以初始化空集合,避免后续重复尝试加载 wordsInFile = new HashSet<>(); } } } } return wordsInFile.contains(word.toLowerCase()); }
关键优化点:
- 用
volatile修饰全局集合,防止多线程下出现“指令重排”导致的未初始化集合访问。 - 双重检查锁:第一次无锁判断避免每次调用都进入同步块,第二次锁内判断防止重复初始化。
- 先加载到临时集合再赋值,避免其他线程拿到未加载完成的集合。
- 处理加载异常,避免无限重复加载失败的资源。
内容的提问来源于stack exchange,提问作者Buffalo
相关产品推荐
相关产品推荐

