让我们假设我们在一个Servlet容器环境Java/J2EE中有这段代码.它是一个基本的Web应用程序.我们有监控工具,这些工具会提到该类在请求线程的执行过程中获得了大部分锁.这仅仅是因为这里的单身做法吗?如果有锁,这段代码会不会遇到争用条件?会导致大量的执行时间吗?锁在单身人士班级上.

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) {
            }
    }
}

Servlet:

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
      SomeSingleton.getInstance().log()
  }

推荐答案

只有一个锁,但不是真正的单例

在单例类上获取Java锁,了解原因

不是"lock"的复数,而是单数.在您的代码中只有一个锁,来自该行:"已同步(日志(log)文件){".

锁在单身人士班级上.

SomeSingleton个类的锁是not,如果这就是你所说的"单例类"的意思.锁位于logfiles对象中持有的Properties对象上.

SomeSingleton

正如第Answer by Eggen节所述,您的类实际上并不是一个单例类.

You have a race condition, where multiple threads could be accessing your getInstance method and read a null before you have finished assigning an instance to SomeSingleton instance. At runtime, your code could actually instantiate more than one SomeSingleton个 objects.

另外,如果您打算通过getInstance访问,则SomeSingleton instance应该是private,而不是public.

retrofit 后,与私有建设者

你需要修改这个代码.我会将其更改为通过public final个对象引用使单个实例可用,并将构造函数设置为私有,以确保不会发生意外的实例化.

更改这一点:

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) {
            }
    }
}

…以下内容.

我们将instance标记为public,因为这是我们指定的调用方法的访问路径.

我们将instance标记为final,以防止重新分配给另一个对象.

为了清楚起见,我们将我们的static个变量重新组织在一起.

我们添加了一个构造函数,将其设置为private以避免不受控制的实例化.

我们在构造函数中初始化logfiles,而不是在声明行中,作为一种风格.像我这样的一些人希望在一个地方看到这个对象的所有初始化,而不是分散在声明行中.理性的人可能不同意.

public class SomeSingleton 
{
    // Statics.
    public static final SomeSingleton instance = null;  
    private static final Thing thing = new Thing();  

    // Member fields.
    private final Properties logfiles;  // To enable logging.
    
    // Private constructor.
    private SomeSingleton() 
    {
        this.logfiles = new Properties() ;
    }

    /**
     * Perform logging by calling:
     * SomeSingleton.instance.log( … )
     */
    public void log( final String message ) 
    {
        synchronized ( this.logfiles ) 
        {
            …  
        }
    }
}

ReentrantLock rather than synchronized for virtual threads

为了与Java 21+中的virtual threads兼容,我们将synchronized替换为ReentrantLock对象.

只有在以下情况下才这样做:(A)正在执行的工作不是完全CPU-bound(涉及阻塞),并且(B)需要一段时间才能执行.如果生命周期 很短,只需使用synchronized.固定虚拟线程在短时间内不是问题.

public class SomeSingleton 
{
    // Statics.
    public static final SomeSingleton instance = null;  
    private static final Thing thing = new Thing();  

    // Member fields.
    private final Properties logfiles;  // To enable logging.
    private final Lock loggingLock;  // To protect logging.
    
    // Private constructor.
    private SomeSingleton() 
    {
        this.logfiles = new Properties() ;
        this.loggingLock = new ReentrantLock();
    }

    /**
     * Perform logging by calling:
     * SomeSingleton.instance.log( … )
     */
    public void log( final String message ) 
    {
        this.loggingLock.lock();  // Blocks until lock becomes available.
        try 
        {
            …  // Involves blocking, such as I/O.
        } 
        finally 
        {
            this.loggingLock.unlock();
        }
    }
}

Singleton as enum

但即便如此,情况也并不理想.单件模式是一件令人惊讶的棘手事情!

目前的看法是,Java中的单例通常最好实现为enum.有关详细信息,请参阅:

public enum SomeSingleton 
{
    // enum
    INSTANCE ;

    // Statics.
    private static final Thing thing = new Thing();  

    // Member fields.
    private final Properties logfiles;  // To enable logging.
    private Lock loggingLock;  // To protect logging.
    
    // Private constructor.
    private SomeSingleton() 
    {
        this.logfiles = new Properties() ;
        this.loggingLock = new ReentrantLock();
    }

    /**
     * Perform logging by calling:
     * SomeSingleton.INSTANCE.log( … )
     */
    public void log( final String message ) 
    {
        this.loggingLock.lock();  // Blocks until lock becomes available.
        try 
        {
            …  // Involves blocking, such as I/O.
        } 
        finally 
        {
            this.loggingLock.unlock();
        }
    }
}

Java相关问答推荐

如何从片段请求数据到活动?在主要活动中单击按钮请求数据?

当耗时的代码完成时,Circular ProgressIndicator显示得太晚

虚拟线程似乎在外部服务调用时阻止运营商线程

Java取消任务运行Oracle查询通过JDBC—连接中断,因为SQLSTATE(08006),错误代码(17002)IO错误:套接字读取中断

流迭代列表<;对象>;上的NoSuchElementException

具有多种令牌类型和段的复杂Java 17正则表达式

我无法获取我的Java Spring应用程序的Logback跟踪日志(log)输出

JDK 21-为什么线程局部随机S nextInt不直接用Super.nextInt实现?

Mac上的全屏截图在使用JavaFX时不能正常工作吗?

如何仅使用键/ID的一部分(组合)高效地返回映射值?

扩展视图高度,并将其拖动到较低的视图上,而不是将其向下推?

SpringBoot:在条件{Variable}.isBlank/{Variable}.isEmpty不起作用的情况下进行路径变量验证

在Ubuntu 23.10上使用mp3创建JavaFX MediaPlayer时出错

try 使用类来包含JSON响应

如何使用MapStrCut转换双向链接

具有最大共同前景像素的图像平移优化算法

在线程Java中调用Interrupt()之后调用Join()

为什么项目名称出现在我的GET请求中?

java 11上出现DateTimeParseException,但java 8上没有

如何在Java上为循环数组从synchronized迁移到ReentrantLock