Answers:
是的,这是必要的。您可以使用几种方法来实现延迟初始化的线程安全性:
Draconian同步:
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
并将被创建,并且由于这是由ClassLoader
s 控制的,因此不需要其他同步。
Draconian synchronization
和Double check synchronization
getInstance()方法必须是静态的!
static
,但如果这样,则可能更有意义。根据要求修改。
r
正确性不需要@MediumOne AFAIK 。这只是避免访问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()
同步。如果不是这样,可能会出现可以创建该类的多个实例的情况。
考虑一下您有两个同时调用的线程的情况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
最后2.你应该做getInstance()
静态。
在多线程环境中用Java实现Singleton的最佳方法是什么?
有关实现Singleton的最佳方法,请参考这篇文章。
当多个线程尝试同时访问getInstance()方法时会发生什么?
这取决于实现该方法的方式。如果使用不带volatile变量的双重锁定,则可能会得到部分构造的Singleton对象。
有关更多详细信息,请参考此问题:
我们可以使单例的getInstance()同步吗?
使用Singleton类时,真的需要同步吗?
如果您通过以下方式实现Singleton,则不需要
请参阅此问题以获取更多详细信息