Java单例类锁问题排查:锁产生原因与风险分析
问题分析与解答
先贴出相关代码:
单例类代码
public class SomeSingleton { private static final Thing object = new Thing(); public static SomeSingleton instance = null; private final Properties logfiles = new Properties(); public static SomeSingleton getInstance() { if (instance == null) { createInstance(); } return instance; } /** * Imagine this method called * SomeSingleton.getInstance().log() */ public void log(final String message) { try { synchronized (logfiles) { // 此处应有实际日志操作,比如写入文件 } } catch (Exception e) { // 省略异常处理 } } }
Servlet代码
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { SomeSingleton.getInstance().log("some message"); }
1. 锁占用是否仅由单例实现方式导致?
不是。监控显示的锁占用主要来自log方法中synchronized (logfiles)的锁竞争,而非单例实现本身:
- 单例实现的问题(线程不安全的
getInstance、instance设为public)会导致多实例创建风险,但不会直接造成锁占用过高。 - 真正的锁占用来源是:所有请求线程都会调用
log方法,竞争同一个logfiles对象的锁(如果单例正常工作,所有线程共用同一个实例的logfiles)。如果synchronized块内有耗时操作(比如磁盘IO写日志),会导致大量线程阻塞等待锁,这才是锁占用高的核心原因。
2. 存在锁的情况下是否会出现竞态条件?
会,且存在两处竞态:
- 单例初始化的竞态:
getInstance方法没有同步控制,多线程可以同时进入if (instance == null)分支,多次调用createInstance(),最终可能创建多个SomeSingleton实例。再加上instance是public修饰,外部可以直接修改这个静态变量,进一步加剧线程安全问题。 - 日志操作的潜在竞态:如果
synchronized块内的日志操作没有被正确同步(比如省略了synchronized直接操作logfiles,或者块内有多个非原子操作),即使有锁,也可能出现日志内容错乱、数据不一致的问题。不过当前代码里synchronized块是空的,若补全实际操作,必须确保所有共享资源的读写都在锁范围内。
3. 是否会导致执行时长过高?
会,尤其是高并发场景下:
- 如果
synchronized块内包含磁盘IO、网络请求等耗时操作,所有请求线程必须串行执行这段代码,大量线程会阻塞在锁等待队列中,每个请求的执行时间会被大幅拉长。 - 单例初始化的竞态如果导致多次实例创建,虽然会分散锁竞争(每个实例有自己的
logfiles锁),但这违背了单例设计初衷,还会造成资源浪费,间接影响系统性能。
内容的提问来源于stack exchange,提问作者Berlin Brown
相关产品推荐
相关产品推荐

