【发布时间】:2012-06-25 07:44:13
【问题描述】:
请澄清我对单例和多线程的疑问:
- 在 Java 中实现 Singleton 的最佳方法是什么? 环境?
- 当多个线程尝试访问
getInstance()时会发生什么 同时使用方法? - 我们可以做单例的
getInstance()synchronized吗? - 在使用单例类时真的需要同步吗?
【问题讨论】:
标签: java multithreading synchronization singleton
请澄清我对单例和多线程的疑问:
getInstance() 时会发生什么
同时使用方法?getInstance()synchronized吗?【问题讨论】:
标签: java multithreading synchronization singleton
是的,这是必要的。您可以使用几种方法通过延迟初始化实现线程安全:
严酷的同步:
private static YourObject instance;
public static synchronized YourObject getInstance() {
if (instance == null) {
instance = new YourObject();
}
return instance;
}
此解决方案要求每个线程同步,而实际上只需要前几个线程。
private static final Object lock = new Object();
private static volatile YourObject instance;
public static YourObject getInstance() {
YourObject r = instance;
if (r == null) {
synchronized (lock) { // While we were waiting for the lock, another
r = instance; // thread may have instantiated the object.
if (r == null) {
r = new YourObject();
instance = r;
}
}
}
return r;
}
此解决方案确保只有前几个尝试获取单例的线程必须通过获取锁的过程。
private static class InstanceHolder {
private static final YourObject instance = new YourObject();
}
public static YourObject getInstance() {
return InstanceHolder.instance;
}
此解决方案利用 Java 内存模型对类初始化的保证来确保线程安全。每个类只能加载一次,只有在需要的时候才会加载。这意味着第一次调用getInstance 时,将加载InstanceHolder 并创建instance,由于这是由ClassLoaders 控制的,因此不需要额外的同步。
【讨论】:
Draconian synchronization 和 Double check synchronization getInstance()-方法必须是静态的!
static,但如果他们是的话可能更有意义。已按要求修改。
r 不需要正确性。这只是避免访问 volatile 字段的优化,因为这比访问局部变量要昂贵得多。
此模式对实例进行线程安全的延迟初始化显式同步!
public class MySingleton {
private static class Loader {
static final MySingleton INSTANCE = new MySingleton();
}
private MySingleton () {}
public static MySingleton getInstance() {
return Loader.INSTANCE;
}
}
它之所以有效,是因为它使用类加载器免费为您完成所有同步:MySingleton.Loader 类首先在 getInstance() 方法内访问,因此 Loader 类在调用 getInstance() 时加载第一次。此外,类加载器保证所有静态初始化在您访问该类之前完成 - 这就是为您提供线程安全的原因。
这就像魔术。
它实际上与 Jhurtado 的枚举模式非常相似,但我发现枚举模式是对枚举概念的滥用(尽管它确实有效)
【讨论】:
final。完成。
getInstance 方法需要参数,我如何将参数传递给内部类中的MySingleton 构造函数?
如果您在 Java 中的多线程环境中工作并且需要保证所有这些线程都在访问一个类的单个实例,您可以使用 Enum。这将具有帮助您处理序列化的额外优势。
public enum Singleton {
SINGLE;
public void myMethod(){
}
}
然后让你的线程像这样使用你的实例:
Singleton.SINGLE.myMethod();
【讨论】:
是的,您需要使getInstance() 同步。如果不是这样,可能会出现可以创建类的多个实例的情况。
考虑您有两个线程同时调用getInstance() 的情况。现在想象一下 T1 刚刚通过 instance == null 检查执行,然后 T2 运行。此时实例尚未创建或设置,因此 T2 将通过检查并创建实例。现在想象执行切换回 T1。现在单例已创建,但 T1 已经完成了检查!它将继续制作对象!使getInstance() 同步可以防止这个问题。
有几种方法可以使单例线程安全,但使 getInstance() 同步可能是最简单的。
【讨论】:
枚举单例
实现线程安全的单例的最简单方法是使用枚举
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
此代码在 Java 1.5 中引入 Enum 后就可以使用
双重检查锁定
如果你想编写一个在多线程环境中工作的“经典”单例(从 Java 1.5 开始),你应该使用这个。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance ;
}
}
这在 1.5 之前不是线程安全的,因为 volatile 关键字的实现不同。
早期加载 Singleton(甚至在 Java 1.5 之前也可以使用)
此实现在加载类时实例化单例并提供线程安全。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
【讨论】:
您还可以使用静态代码块在类加载时实例化实例并防止线程同步问题。
public class MySingleton {
private static final MySingleton instance;
static {
instance = new MySingleton();
}
private MySingleton() {
}
public static MySingleton getInstance() {
return instance;
}
}
【讨论】:
instance 设为final 2. 你应该将getInstance() 设为静态。
在多线程环境中,在 Java 中实现 Singleton 的最佳方法是什么?
请参阅这篇文章了解实现单例的最佳方法。
What is an efficient way to implement a singleton pattern in Java?
当多个线程尝试同时访问 getInstance() 方法时会发生什么?
这取决于您实现该方法的方式。如果您使用没有 volatile 变量的双重锁定,您可能会得到部分构造的 Singleton 对象。
更多详情请参考这个问题:
Why is volatile used in this example of double checked locking
我们可以让单例的 getInstance() 同步吗?
在使用单例类时真的需要同步吗?
如果您通过以下方式实现 Singleton,则不需要
更多详情请参考这个问题
【讨论】:
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis () {...}
}
来源:Effective Java -> Item 2
如果您确定该类将始终保持单例,则建议使用它。
【讨论】: