假设我们在 servlet 容器环境 java/j2ee 中有此代码。它是一个基本的网络应用程序。我们有监控工具提到此类在请求线程执行期间获得了大部分锁定。这仅仅是因为这里的单例方法吗?如果有锁,这段代码是否会遇到竞争条件?导致大量执行时间?锁位于单例类上。
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) {
}
}
}
Run Code Online (Sandbox Code Playgroud)
小服务程序:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
SomeSingleton.getInstance().log()
}
Run Code Online (Sandbox Code Playgroud)
enum
.ReentrantLock
使用长时间运行(阻塞)的代码保护线程不安全的资源。仅在不阻塞的简短代码周围使用synchronized
,例如检查内存中的防护。\n\n在单例类上获取 Java 锁,了解原因
\n
“锁”不是复数,而是单数。您的代码中只有一个锁,从这一行开始:“synchronized (logfiles) {”。
\n\n\n锁位于单例类上。
\n
如果这就是您所说的“单例类”的意思,那么锁不在您的类上。SomeSingleton
锁位于对象Properties
中持有的对象上logfiles
。
\n\n\n
SomeSingleton
正如Eggen 的 Answer中所解释的,您的类实际上并不是单例。
\n您有一个竞争条件,其中多个线程可能正在访问您的方法并在您完成将实例分配给 之前getInstance
读取 a 。在运行时,您的代码实际上可以实例化多个对象。null
SomeSingleton instance
SomeSingleton
另外,如果您打算通过 进行访问,则SomeSingleton instance
应该是private
而不是。public
getInstance
您需要修改该代码。我将对其进行更改,以通过对象引用提供单个实例public final
,并将构造函数设为私有以确保不会发生无意的实例化。
改变这个:
\npublic class SomeSingleton {\n\n private static final Thing object = new Thing();\n\n public static SomeSingleton instance = null; \n\n private final Properties logfiles = new Properties();\n\n public static SomeSingleton getInstance() {\n if (instance == null) {\n createInstance();\n }\n return instance;\n }\n\n \n /**\n * Imagine this method called\n * SomeSingleton.getInstance().log()\n */\n public void log(final String message) {\n try {\n synchronized (logfiles) {\n }\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 改为以下内容。
\n我们标记instance
为是public
因为这是我们指定的调用方法的访问路径。
我们标记instance
为final
以防止重新分配给另一个对象。
为了清晰起见,我们将变量重新static
组织在一起。
我们添加一个构造函数,使其private
避免不受控制的实例化。
我们初始化logfiles
作为风格问题,像我这样的一些人希望在一个地方看到该对象的所有初始化,而不是分散在声明行中。理性的人可能会不同意。
此代码instance
在静态声明中实例化\xe2\x80\xa6 instance = new SomeSingleton();
。我们不再需要你的getInstance
方法了。该实例化在类加载时发生。该instance
字段已标记final
以防止分配任何其他实例。
public class SomeSingleton \n{\n // Statics.\n public static final SomeSingleton instance = new SomeSingleton();\n private static final Thing thing = new Thing(); \n\n // Member fields.\n private final Properties logfiles; // To enable logging.\n \n // Private constructor.\n private SomeSingleton() \n {\n this.logfiles = new Properties() ;\n }\n\n /**\n * Perform logging by calling:\n * SomeSingleton.instance.log( \xe2\x80\xa6 )\n */\n public void log( final String message ) \n {\n synchronized ( this.logfiles ) \n {\n \xe2\x80\xa6 \n }\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\nReentrantLock
而不是synchronized
虚拟线程为了与Java 21 +中的虚拟线程兼容,我们替换为synchronized
ReentrantLock
对象替换。
仅当 (a) 正在执行的工作不完全受CPU 限制(涉及阻塞)并且 (b) 需要一段时间才能执行时才执行此操作。如果短暂的话,只需使用synchronized
. 固定虚拟线程暂时不是问题。
public class SomeSingleton \n{\n // Statics.\n public static final SomeSingleton instance = new SomeSingleton();\n private static final Thing thing = new Thing(); \n\n // Member fields.\n private final Properties logfiles; // To enable logging.\n private final Lock loggingLock; // To protect logging.\n \n // Private constructor.\n private SomeSingleton() \n {\n this.logfiles = new Properties() ;\n this.loggingLock = new ReentrantLock();\n }\n\n /**\n * Perform logging by calling:\n * SomeSingleton.instance.log( \xe2\x80\xa6 )\n */\n public void log( final String message ) \n {\n this.loggingLock.lock(); // Blocks until lock becomes available.\n try \n {\n \xe2\x80\xa6 // Involves blocking, such as I/O.\n } \n finally \n {\n this.loggingLock.unlock();\n }\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\nenum
但即使这样也并不理想。单例模式是一件非常棘手的事情!
\n当前的观点是,Java 中的单例通常最好实现为enum
. 有关更多信息,请参阅:
枚举是隐式的static
and final
,无需添加这些修饰符。
在转换为枚举时,我们可以删除这一行:public static final SomeSingleton instance = new SomeSingleton();
。当类加载时,命名的枚举对象会填充(我们的单例的实例化),就像我们static
上面提到的字段一样。
public enum SomeSingleton \n{\n // enum\n INSTANCE ;\n\n // Statics.\n private static final Thing thing = new Thing(); \n\n // Member fields.\n private final Properties logfiles; // To enable logging.\n private final Lock loggingLock; // To protect logging.\n \n // Private constructor.\n private SomeSingleton() \n {\n this.logfiles = new Properties() ;\n this.loggingLock = new ReentrantLock();\n }\n\n /**\n * Perform logging by calling:\n * SomeSingleton.INSTANCE.log( \xe2\x80\xa6 )\n */\n public void log( final String message ) \n {\n this.loggingLock.lock(); // Blocks until lock becomes available.\n try \n {\n \xe2\x80\xa6 // Involves blocking, such as I/O.\n } \n finally \n {\n this.loggingLock.unlock();\n }\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
归档时间: |
|
查看次数: |
127 次 |
最近记录: |