在Java中实现单例模式的有效方法是什么? [关闭]

在Java中实现单例模式的有效方法是什么?


#1楼

我使用Spring框架来管理我的单身人士。 它不会强制类的“单一性”(如果涉及多个类加载器,您将无法真正做到),但是它提供了一种非常简单的方法来构建和配置用于创建不同类型对象的不同工厂。


#2楼

我会说Enum singleton

在Java中使用枚举的Singleton通常是声明枚举Singleton的方法。 枚举单例可能包含实例变量和实例方法。 为简单起见,还请注意,如果您正在使用任何实例方法,则如果该方法完全影响对象的状态,则需要确保该方法的线程安全性。

枚举的使用非常容易实现,并且对于可序列化的对象没有任何缺点,必须以其他方式加以避免。

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

您可以通过Singleton.INSTANCE进行访问,这比在Singleton上调用getInstance()方法要容易得多。

1.12枚举常量的序列化

枚举常量的序列化与普通可序列化或可外部化的对象不同。 枚举常量的序列化形式仅由其名称组成; 常量的字段值不存在于表单中。 为了序列化枚举常量, ObjectOutputStream写入由枚举常量的name方法返回的值。 为了反序列化枚举常量, ObjectInputStream从流中读取常量名称。 然后,通过调用java.lang.Enum.valueOf方法,将常量的枚举类型与接收到的常量名称一起作为参数来获取反序列化的常量。 像其他可序列化或可外部化的对象一样,枚举常量可以用作随后出现在序列化流中的反向引用的目标。

枚举常量的序列化过程无法自定义:在枚举和反序列化过程中,将忽略枚举类型定义的任何特定于类的writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve方法。 同样,任何serialPersistentFieldsserialVersionUID字段声明也将被忽略-所有枚举类型的固定serialVersionUID0L 。 不需要为枚举类型记录可序列化的字段和数据,因为发送的数据类型没有变化。

引用Oracle文档

传统Singleton的另一个问题是,一旦实现了Serializable接口,它们就不再保持Singleton了,因为readObject()方法总是返回Java构造函数之类的新实例。 可以通过使用readResolve()来避免这种情况,并通过替换为如下所示的单例来丢弃新创建的实例

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

如果您的Singleton Class保持状态,这可能会变得更加复杂,因为您需要使它们处于瞬态状态,但是在Enum Singleton中,JVM保证了序列化。


好读

  1. 单例模式
  2. 枚举,单例和反序列化
  3. 双重检查锁定和单例模式

#3楼

以下是3种不同的方法

1)枚举

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2)仔细检查锁定/延迟加载

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private static volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public static DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

3)静态工厂方法

/**
* Singleton pattern example with static factory method
*/

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

#4楼

免责声明:我刚刚总结了所有很棒的答案,并用我的话写下来。


在实施Singleton时,我们有2个选择
1.延迟加载
2.提早加载

延迟加载会增加一些开销(说实话,很多),因此仅当您有非常大的对象或繁重的构造代码,并且还需要在需要实例之前使用其他可访问的静态方法或字段时才使用它您需要使用延迟初始化。否则,选择早期加载是一个不错的选择。

实现Singleton的最简单方法是

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }
}

除了早期加载的单例外,其他一切都很好。 让我们尝试延迟加载的单例

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT  
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

到目前为止还算不错,但我们的英雄无法与多个邪恶的线单独战斗,而邪恶的线想要我们的英雄很多实例。 因此,让我们保护它免受邪恶的多线程攻击

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

但这还不足以保护英雄,真的!!! 这是我们可以/应该做的最好的事情来帮助我们的英雄

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

这称为“双重检查锁定习惯用法”。 容易忘记易失性陈述,也很难理解为什么这样做是必要的。
有关详细信息: http : //www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

现在我们可以确定邪恶线程,但是残酷的序列化又如何呢? 我们必须确保即使反序列化也不会创建新对象

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

readResolve()方法readResolve()确保返回唯一的实例,即使该对象在程序的先前运行中已序列化也是如此。

最终,我们添加了足够的针对线程和序列化的保护,但是我们的代码看起来笨拙。 让我们给英雄改头换面

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    // Damn you serialization
    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

是的,这是我们非常相同的英雄:)
由于该行的private static final Foo INSTANCE = new Foo(); 仅在实际使用类FooLoader时执行,这会处理惰性实例化,

并且保证是线程安全的。

到目前为止,我们已经做到了,这是实现我们所做的一切的最好方法,也是最好的方法

 public enum Foo {
       INSTANCE;
   }

在内部将被视为

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

那就不用再担心序列化,线程和丑陋的代码了。 ENUMS单例也被延迟初始化

此方法在功能上与公共领域方法等效,除了它更简洁,免费提供序列化机制,并且即使面对复杂的序列化或反射攻击,也可以防止多重实例化。 尽管此方法尚未被广泛采用,但是单元素枚举类型是实现单例的最佳方法。

-“有效Java”中的约书亚·布洛赫(Joshua Bloch)

现在您可能已经意识到为什么将ENUMS视为实现Singleton的最佳方法了,感谢您的耐心配合:)
在我的博客上更新了它。


#5楼

对于JSE 5.0及更高版本,请采用Enum方法,否则请使用静态单例持有人方法((由Bill Pugh描述的一种惰性加载方法)。后者解决方案也是线程安全的,不需要特殊的语言构造(即volatile或sync)。


#6楼

最简单的单例类

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}

#7楼

public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
    if (INSTANCE != null)
        throw new IllegalStateException (“Already instantiated...”);
}

    public synchronized static Singleton getInstance() { 
    return INSTANCE;

    }

}

由于我们在getInstance之前添加了Synchronized关键字,因此在两个线程同时调用getInstance的情况下,我们避免了竞争情况。


#8楼

我仍然认为在Java 1.5之后,枚举是可用的最佳可用单例实现,因为它还确保即使在多线程环境中也只能创建一个实例。

public enum Singleton{ INSTANCE; }

你完成了!!!


#9楼

版本1:

public class MySingleton {
    private static MySingleton instance = null;
    private MySingleton() {}
    public static synchronized MySingleton getInstance() {
        if(instance == null) {
            instance = new MySingleton();
        }
        return instance;
    }
}

延迟加载,螺纹,因为阻断,低性能安全的synchronized

版本2:

public class MySingleton {
    private MySingleton() {}
    private static class MySingletonHolder {
        public final static MySingleton instance = new MySingleton();
    }
    public static MySingleton getInstance() {
        return MySingletonHolder.instance;
    }
}

延迟加载,线程安全,无阻塞,高性能。


#10楼

看看这篇文章。

Java核心库中的GoF设计模式示例

在最佳答案的“单身”部分中,

单例(可通过创建方法识别,每次返回相同实例(通常是其自身))

  • java.lang.Runtime#getRuntime()
  • java.awt.Desktop#getDesktop()
  • java.lang.System#getSecurityManager()

您还可以从Java本机类本身学习Singleton的示例。


#11楼

制作单例对象的多种方法:

  1. 根据Joshua Bloch的说法-枚举将是最好的。

  2. 您也可以使用双重检查锁定。

  3. 甚至可以使用内部静态类。


#12楼

枚举单例

实现单线程安全的最简单方法是使用枚举

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");
  }

}

#13楼

这是实现简单singleton

public class Singleton {
    // It must be static and final to prevent later modification
    private static final Singleton INSTANCE = new Singleton();
    /** The constructor must be private to prevent external instantiation */ 
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

这是正确延迟创建singleton

public class Singleton {
    // The constructor must be private to prevent external instantiation   
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /** 
     * The static inner class responsible for creating your instance only on demand,
     * because the static fields of a class are only initialized when the class
     * is explicitly called and a class initialization is synchronized such that only 
     * one thread can perform it, this rule is also applicable to inner static class
     * So here INSTANCE will be created only when SingletonHolder.INSTANCE 
     * will be called
     */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

#14楼

通常反对Singleton的另一个论点是它们的可测试性问题。 单例不容易被嘲笑用于测试目的。 如果发现这是个问题,我想做一下以下修改:

public class SingletonImpl {

    private static SingletonImpl instance;

    public static SingletonImpl getInstance() {
        if (instance == null) {
            instance = new SingletonImpl();
        }
        return instance;
    }

    public static void setInstance(SingletonImpl impl) {
        instance = impl;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

添加的setInstance方法允许在测试期间设置单例类的模型实现:

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

这也适用于早期的初始化方法:

public class SingletonImpl {

    private static final SingletonImpl instance = new SingletonImpl();

    private static SingletonImpl alt;

    public static void setInstance(SingletonImpl inst) {
        alt = inst;
    }

    public static SingletonImpl getInstance() {
        if (alt != null) {
            return alt;
        }
        return instance;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

这也具有将该功能也暴露给普通应用程序的缺点。 可能会尝试使用该代码的其他开发人员使用“ setInstance”方法来更改特定功能,从而更改整个应用程序的行为,因此,此方法在它的javadoc中至少应包含一个很好的警告。

尽管如此,为了进行模型测试(必要时),此代码公开可能是可以接受的价格。


#15楼

There are 4 ways to create a singleton in java.

1- eager initialization singleton

    public class Test{
        private static final Test test = new Test();
        private Test(){}
        public static Test getTest(){
            return test;
        }
    }

2- lazy initialization singleton (thread safe)

    public class Test {
         private static volatile Test test;
         private Test(){}
         public static Test getTest() {
            if(test == null) {
                synchronized(Test.class) {
                    if(test == null){test = new Test();
                }
            }
         }

        return test;
    }


3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)

    public class Test {

        private Test(){}

        private static class TestHolder{
            private static final Test test = new Test();
        }

        public static Test getInstance(){
            return TestHolder.test;
        }
    }

4- enum singleton
      public enum MySingleton {
        INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}

#16楼

在这方面可能要晚一些,但是实现单例有很多细微差别。 持有人模式无法在许多情况下使用。 和IMO在使用volatile时-还应该使用局部变量。 让我们从头开始,迭代问题。 您会明白我的意思的。


第一次尝试可能看起来像这样:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

在这里,我们有MySingleton类,该类具有一个称为INSTANCE的私有静态成员,以及一个名为getInstance()的公共静态方法。 第一次调用getInstance()时,INSTANCE成员为null。 然后,流程将进入创建条件,并创建MySingleton类的新实例。 随后对getInstance()的调用将发现已经设置了INSTANCE变量,因此不会创建另一个MySingleton实例。 这样可以确保只有一个MySingleton实例可以在所有getInstance()调用方之间共享。

但是这种实现有一个问题。 多线程应用程序在创建单个实例时将具有竞争条件。 如果多个执行线程同时(或前后)命中getInstance()方法,则它们各自将INSTANCE成员视为null。 这将导致每个线程创建一个新的MySingleton实例,然后设置INSTANCE成员。


private static MySingleton INSTANCE;

public static synchronized MySingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MySingleton();
    }

    return INSTANCE;
}

在这里,我们在方法签名中使用了synced关键字来同步getInstance()方法。 这肯定会解决我们的比赛条件。 现在,线程将一次阻塞并进入一种方法。 但这也会造成性能问题。 此实现不仅同步单个实例的创建,而且还同步所有对getInstance()的调用,包括读取。 读取不需要同步,因为它们仅返回INSTANCE的值。 由于读操作将占我们调用的大部分(请记住,实例化仅在第一个调用上发生),因此,通过同步整个方法将导致不必要的性能损失。


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

在这里,我们将同步从方法签名移到了一个同步块,该块包装了MySingleton实例的创建。 但这能解决我们的问题吗? 好了,我们不再阻止读取,但是我们也向后退了一步。 多个线程将同时或大约同时访问getInstance()方法,并且它们都将INSTANCE成员视为null。 然后,他们将命中同步块,在该块中将获得锁并创建实例。 当该线程退出该块时,其他线程将争夺该锁,并且每个线程将一个一个地插入该块并创建我们类的新实例。 现在我们回到了起点。


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

在这里,我们从INSIDE块发出另一张支票。 如果已经设置了INSTANCE成员,我们将跳过初始化。 这称为双重检查锁定。

这解决了我们的多重实例化问题。 但是,我们的解决方案再次提出了另一个挑战。 其他线程可能不会“看到” INSTANCE成员已更新。 这是因为Java如何优化内存操作。 线程将变量的原始值从主内存复制到CPU的缓存中。 然后,将对值的更改写入该缓存并从中读取。 这是Java旨在优化性能的功能。 但这给我们的单例实现带来了问题。 第二个线程(由不同的CPU或内核使用不同的缓存处理)将看不到第一个线程所做的更改。 这将导致第二个线程将INSTANCE成员视为null,从而迫使创建我们的单例的新实例。


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

我们通过在INSTANCE成员的声明中使用volatile关键字来解决此问题。 这将告诉编译器始终读取和写入主内存,而不是CPU缓存。

但是这种简单的改变是有代价的。 因为我们绕过CPU缓存,所以每次对易失性INSTANCE成员进行操作时,都会造成性能下降-我们执行4次。 我们仔细检查存在性(1和2),设置值(3),然后返回值(4)。 有人可能会说这条路径是边缘情况,因为我们仅在方法的第一次调用期间创建实例。 也许对创作的性能冲击是可以容忍的。 但是,即使是我们的主要用例,也将对易失性成员运行两次。 一次检查是否存在,再次返回其值。


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

由于性能下降是由于直接对volatile成员进行操作,所以我们将局部变量设置为volatile的值,然后对局部变量进行操作。 这将减少我们对挥发物进行操作的次数,从而收回我们损失的部分性能。 注意,当我们进入同步块时,我们必须再次设置局部变量。 这样可以确保它与我们等待锁时发生的任何更改都是最新的。

我最近写了一篇有关此的文章。 解构Singleton 。 您可以在这些示例中找到更多信息以及“ holder”模式的示例。 还有一个真实的示例,展示了双重检查的易失性方法。 希望这可以帮助。


#17楼

我见过的最好的单例模式使用Supplier接口。

  • 它是通用且可重用的
  • 它支持延迟初始化
  • 它只有在初始化之前才被同步,然后将阻塞供应商替换为非阻塞供应商。

见下文:

public class Singleton<T> implements Supplier<T> {

    private boolean initialized;
    private Supplier<T> singletonSupplier;

    public Singleton(T singletonValue) {
        this.singletonSupplier = () -> singletonValue;
    }

    public Singleton(Supplier<T> supplier) {
        this.singletonSupplier = () -> {
            // The initial supplier is temporary; it will be replaced after initialization
            synchronized (supplier) {
                if (!initialized) {
                    T singletonValue = supplier.get();
                    // Now that the singleton value has been initialized,
                    // replace the blocking supplier with a non-blocking supplier
                    singletonSupplier = () -> singletonValue;
                    initialized = true;
                }
                return singletonSupplier.get();
            }
        };
    }

    @Override
    public T get() {
        return singletonSupplier.get();
    }
}

#18楼

如果需要延迟加载类的实例变量,则需要仔细检查习惯用法。 如果需要延迟加载静态变量或单例,则需要按需持有者习惯进行初始化

另外,如果单例需要可序列化,则所有其他字段都必须是瞬态的,并且需要实现readResolve()方法以保持单例对象不变。 否则,每次反序列化对象时,都会创建该对象的新实例。 readResolve()所做的是用readObject()替换读取的新对象,由于没有变量引用该对象,因此该对象被强制垃圾回收。

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 

#19楼

确保您确实需要它。 用谷歌搜索“单反模式”,看看反对它的论点。 我想这没有天生的错误,但这只是公开某些全局资源/数据的一种机制,因此请确保这是最好的方法。 特别是,我发现依赖注入更有用,特别是在您还使用单元测试的情况下,因为DI允许您将模拟资源用于测试目的。


#20楼

Wikipedia也有一些单例的示例 ,也使用Java。 Java 5实现看起来很完整,并且是线程安全的(应用了双重检查锁定)。


#21楼

真正考虑一下为什么在编写之前需要单身人士。 关于使用它们的准宗教辩论,如果您用Java搜索单身人士,就很容易绊倒。

就我个人而言,由于多种原因,我会尽量避免单身人士,而再次通过搜索单身人士可以找到大多数原因。 我觉得很多时候滥用单例是因为它们易于为每个人所理解,它们被用作将“全局”数据输入到OO设计中的一种机制,并且它们被使用是因为它很容易规避对象生命周期管理(或真正在考虑如何从B内部做A)。 查看诸如控制反转(IoC)或依赖注入(DI)之类的东西,可以找到一个很好的中间立场。

如果您真的需要一个,那么维基百科提供了一个正确实现单例的很好的例子。


#22楼

忘了懒惰的初始化 ,这太成问题了。 这是最简单的解决方案:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}

#23楼

如果您不需要延迟加载,则只需尝试

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() { return Singleton.INSTANCE; }

    protected Object clone() {
        throw new CloneNotSupportedException();
    }
}

如果您想要延迟加载,并且希望Singleton是线程安全的,请尝试仔细检查模式

public class Singleton {
        private static Singleton instance = null;

        private Singleton() {}

        public static Singleton getInstance() { 
              if(null == instance) {
                  synchronized(Singleton.class) {
                      if(null == instance) {
                          instance = new Singleton();
                      }
                  }
               }
               return instance;
        }

        protected Object clone() {
            throw new CloneNotSupportedException();
        }
}

由于不能保证双重检查模式有效(由于编译器的某些问题,我对此一无所知。),您还可以尝试同步整个getInstance方法或为所有Singleton创建一个注册表。


#24楼

Java 5+中的线程安全:

class Foo {
    private static volatile Bar bar = null;
    public static Bar getBar() {
        if (bar == null) {
            synchronized(Foo.class) {
                if (bar == null)
                    bar = new Bar(); 
            }
        }
        return bar;
    }
}

编辑 :在这里注意volatile修饰符。 :)这很重要,因为如果没有它,JMM(Java内存模型)将无法保证其他线程无法看到其值的更改。 同步并不会解决这个问题,它只会序列化对该代码块的访问。

编辑2 :@Bno的答案详细说明了比尔·普格(FindBugs)建议的方法,并且可以说是更好的方法。 去阅读并投票支持他的答案。


#25楼

使用一个枚举:

public enum Foo {
    INSTANCE;
}

约书亚·布洛赫(Joshua Bloch)在Google I / O 2008上的“ 有效的Java重新加载”演讲中解释了这种方法: 链接到视频 。 另见幻灯片他的介绍(30-32 effective_java_reloaded.pdf ):

实现可序列化单例的正确方法

 public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } } 

编辑: “有效Java”在线部分说:

“该方法在功能上等同于公共领域方法,除了它更简洁,免费提供序列化机制,甚至在面对复杂的序列化或反射攻击时也提供了针对多重实例化的铁定保证。尚未被广泛采用的单元素枚举类型是实现单例的最佳方法 。”


#26楼

我对某些答案感到迷惑不解,这些答案建议使用DI作为使用单例的替代方法。 这些是不相关的概念。 您可以使用DI注入单例或非单例(例如,每线程)实例。 至少如果您使用Spring 2.x,这是正确的,我不能代表其他DI框架。

因此,我对OP的回答将是(除了最琐碎的示例代码之外):

  1. 使用像Spring这样的DI框架,然后
  2. 无论您的依赖项是单例,请求范围,会话范围还是任何其他类型,都将其作为DI配置的一部分。

这种方法为您提供了一个很好的解耦(因此是灵活且可测试的)体系结构,其中是否使用单例是易于逆转的实现细节(前提是您使用的任何单例都是线程安全的)。


#27楼

Stu Thompson发布的解决方案在Java5.0及更高版本中有效。 但是我不希望使用它,因为我认为它容易出错。

容易忘记易失性陈述,也很难理解为什么这样做是必要的。 如果没有volatile,由于经过了双重检查的锁定反模式,该代码将不再是线程安全的。 有关更多信息,请参见《 Java并发实践》第16.2.4段。 简而言之:此模式(在Java5.0之前或没有volatile语句)可能返回对(仍然)处于错误状态的Bar对象的引用。

发明这种模式是为了优化性能。 但这真的不再是真正的问题。 以下惰性初始化代码快速而且更重要的是更易于阅读。

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}

#28楼

根据用法,有几个“正确”的答案。

由于java5的最佳方法是使用枚举:

public enum Foo {
   INSTANCE;
}

在Java5之前的版本中,最简单的情况是:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

让我们来看一下代码。 首先,您希望课程是最终的。 在这种情况下,我使用了final关键字让用户知道它是final。 然后,您需要将构造函数设为私有,以防止用户创建自己的Foo。 从构造函数中抛出异常会阻止用户使用反射创建第二个Foo。 然后,创建一个private static final Foo字段来保存唯一的实例,并创建一个public static Foo getInstance()方法来返回它。 Java规范确保仅在首次使用该类时才调用构造函数。

如果您有一个很大的对象或繁重的构造代码,并且还需要在需要实例之前使用其他可访问的静态方法或字段,则仅在那时才需要使用惰性初始化。

您可以使用private static class来加载实例。 代码如下:

public final class Foo {

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }
}

由于该行的private static final Foo INSTANCE = new Foo(); 仅在实际使用类FooLoader时才执行,这会处理延迟实例化,并确保它是线程安全的。

如果还希望序列化对象,则需要确保反序列化不会创建副本。

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

readResolve()方法readResolve()确保返回唯一的实例,即使对象在程序的先前运行中已被序列化也是如此。


#29楼

有时,简单的“ static Foo foo = new Foo(); ”是不够的。 只需考虑您要执行的一些基本数据插入。

另一方面,您将必须同步任何实例化此类单例变量的方法。 同步本身还不错,但是它可能导致性能问题或锁定(在非常罕见的情况下,使用此示例。解决方案是

public class Singleton {

    private static Singleton instance = null;

    static {
          instance = new Singleton();
          // do some of your instantiation stuff here
    }

    private Singleton() {
          if(instance!=null) {
                  throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
          }
    }

    public static getSingleton() {
          return instance;
    }

}

现在会发生什么? 该类是通过类加载器加载的。 从字节数组解释了类之后,VM会立即执行静态{} -块。 这就是全部秘密:在此类加载器加载给定包的给定类(名称)时,只会调用一次static-block。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值