159

我正在阅读 Wikipedia 上的 Singleton 文章,我遇到了这个例子:

public class Singleton {
    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }

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

虽然我真的很喜欢这个 Singleton 的行为方式,但我看不出如何调整它以将参数合并到构造函数中。在 Java 中执行此操作的首选方法是什么?我必须做这样的事情吗?

public class Singleton
{
    private static Singleton singleton = null;  
    private final int x;

    private Singleton(int x) {
        this.x = x;
    }

    public synchronized static Singleton getInstance(int x) {
        if(singleton == null) singleton = new Singleton(x);
        return singleton;
    }
}

谢谢!


编辑:我想我对使用 Singleton 的渴望已经引发了一场争议风暴。让我解释一下我的动机,希望有人能提出一个更好的主意。我正在使用网格计算框架来并行执行任务。一般来说,我有这样的事情:

// AbstractTask implements Serializable
public class Task extends AbstractTask
{
    private final ReferenceToReallyBigObject object;

    public Task(ReferenceToReallyBigObject object)
    {
        this.object = object;
    }

    public void run()
    {
        // Do some stuff with the object (which is immutable).
    }
}

发生的情况是,即使我只是将我的数据的引用传递给所有任务,当任务被序列化时,数据也会被一遍又一遍地复制。我想做的是在所有任务之间共享对象。当然,我可能会像这样修改类:

// AbstractTask implements Serializable
public class Task extends AbstractTask
{
    private static ReferenceToReallyBigObject object = null;

    private final String filePath;

    public Task(String filePath)
    {
        this.filePath = filePath;
    }

    public void run()
    {
        synchronized(this)
        {
            if(object == null)
            {
                ObjectReader reader = new ObjectReader(filePath);
                object = reader.read();
            }
        }

        // Do some stuff with the object (which is immutable).
    }
}

如您所见,即使在这里我也有一个问题,即在通过第一个文件路径后,传递不同的文件路径没有任何意义。这就是为什么我喜欢在答案中发布的商店的想法。无论如何,我不想在 run 方法中包含加载文件的逻辑,而是想将此逻辑抽象为一个 Singleton 类。我不会再举一个例子,但我希望你能明白。请让我听听您的想法,以更优雅的方式完成我正在尝试做的事情。再次感谢你!

4

21 回答 21

192

我会非常清楚地表明我的观点:带参数的单例不是单例

根据定义,单例是您希望实例化不超过一次的对象。如果您尝试将参数提供给构造函数,那么单例的意义何在?

你有两个选择。如果你想用一些数据初始化你的单例,你可以在实例化之后用数据加载它,像这样:

SingletonObj singleton = SingletonObj.getInstance();
singleton.init(paramA, paramB); // init the object with data

如果您的单例正在执行的操作是重复的,并且每次使用不同的参数,您不妨将参数传递给正在执行的 main 方法:

SingletonObj singleton = SingletonObj.getInstance();
singleton.doSomething(paramA, paramB); // pass parameters on execution

在任何情况下,实例化始终是无参数的。否则你的单身人士不是单身人士。

于 2009-06-26T20:18:32.813 回答
43

我认为您需要像工厂这样的东西来实例化和重用具有各种参数的对象。它可以通过使用同步HashMapConcurrentHashMap将参数(Integer例如一个)映射到您的“单例”可参数化类来实现。

尽管您可能已经到了应该使用常规的非单例类的地步(例如,需要 10.000 个不同参数化的单例)。

这是此类商店的示例:

public final class UsefulObjFactory {

    private static Map<Integer, UsefulObj> store =
        new HashMap<Integer, UsefulObj>();

    public static final class UsefulObj {
        private UsefulObj(int parameter) {
            // init
        }
        public void someUsefulMethod() {
            // some useful operation
        }
    }

    public static UsefulObj get(int parameter) {
        synchronized (store) {
            UsefulObj result = store.get(parameter);
            if (result == null) {
                result = new UsefulObj(parameter);
                store.put(parameter, result);
            }
            return result;
        }
    }
}

为了更进一步,Java enums 也可以被认为(或用作)参数化单例,尽管只允许固定数量的静态变体。

但是,如果您需要分布式1解决方案,请考虑一些横向缓存解决方案。例如:EHCache、Terracotta 等。

1在跨越可能多台计算机上的多个 VM 的意义上。

于 2009-06-26T20:12:31.297 回答
34

您可以添加一个可配置的初始化方法,以便将实例化与获取分开。

public class Singleton {
    private static Singleton singleton = null;
    private final int x;

    private Singleton(int x) {
        this.x = x;
    }

    public static Singleton getInstance() {
        if(singleton == null) {
            throw new AssertionError("You have to call init first");
        }

        return singleton;
    }

    public synchronized static Singleton init(int x) {
        if (singleton != null)
        {
            // in my opinion this is optional, but for the purists it ensures
            // that you only ever get the same instance when you call getInstance
            throw new AssertionError("You already initialized me");
        }

        singleton = new Singleton(x);
        return singleton;
    }

}

然后你可以调用Singleton.init(123)一次来配置它,例如在你的应用程序启动中。

于 2016-09-27T18:01:57.477 回答
14

如果要显示某些参数是必需的,也可以使用 Builder 模式。

    public enum EnumSingleton {

    INSTANCE;

    private String name; // Mandatory
    private Double age = null; // Not Mandatory

    private void build(SingletonBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    // Static getter
    public static EnumSingleton getSingleton() {
        return INSTANCE;
    }

    public void print() {
        System.out.println("Name "+name + ", age: "+age);
    }


    public static class SingletonBuilder {

        private final String name; // Mandatory
        private Double age = null; // Not Mandatory

        private SingletonBuilder(){
          name = null;
        }

        SingletonBuilder(String name) {
            this.name = name;
        }

        public SingletonBuilder age(double age) {
            this.age = age;
            return this;
        }

        public void build(){
            EnumSingleton.INSTANCE.build(this);
        }

    }


}

然后您可以按如下方式创建/实例化/参数化它:

public static void main(String[] args) {
    new EnumSingleton.SingletonBuilder("nico").age(41).build();
    EnumSingleton.getSingleton().print();
}
于 2014-10-06T13:54:18.563 回答
9

很惊讶没有人提到如何创建/检索记录器。例如,下面显示了如何检索Log4J 记录器。

// Retrieve a logger named according to the value of the name parameter. If the named logger already exists, then the existing instance will be returned. Otherwise, a new instance is created.
public static Logger getLogger(String name)

有一些级别的间接,但关键部分是下面的方法,它几乎说明了它是如何工作的。它使用哈希表来存储现有的记录器,并且密钥来自名称。如果给定名称不存在记录器,它会使用工厂创建记录器,然后将其添加到哈希表中。

69   Hashtable ht;
...
258  public
259  Logger getLogger(String name, LoggerFactory factory) {
260    //System.out.println("getInstance("+name+") called.");
261    CategoryKey key = new CategoryKey(name);
262    // Synchronize to prevent write conflicts. Read conflicts (in
263    // getChainedLevel method) are possible only if variable
264    // assignments are non-atomic.
265    Logger logger;
266
267    synchronized(ht) {
268      Object o = ht.get(key);
269      if(o == null) {
270        logger = factory.makeNewLoggerInstance(name);
271        logger.setHierarchy(this);
272        ht.put(key, logger);
273        updateParents(logger);
274        return logger;
275      } else if(o instanceof Logger) {
276        return (Logger) o;
277      } 
...
于 2015-10-17T23:56:46.467 回答
6

带参数的单例不是单例”的说法并不完全正确。我们需要从应用程序的角度而不是从代码的角度来分析这一点。

我们构建单例类以在一个应用程序运行中创建一个对象的单个实例。通过使用带参数的构造函数,您可以在代码中构建灵活性,以便在每次运行应用程序时更改单例对象的某些属性。这不违反单例模式。如果从代码的角度来看,这看起来像是违规。

设计模式可以帮助我们编写灵活和可扩展的代码,而不是阻碍我们编写好的代码。

于 2016-04-19T11:30:02.650 回答
4

使用 getter 和 setter 设置变量并使默认构造函数私有。然后使用:

Singleton.getInstance().setX(value);
于 2009-06-26T20:07:30.013 回答
4

修改使用Bill Pugh 的按需初始化持有人习语的 Singleton 模式。这是线程安全的,没有专门的语言结构(即 volatile 或 synchronized)的开销:

public final class RInterfaceHL {

    /**
     * Private constructor prevents instantiation from other classes.
     */
    private RInterfaceHL() { }

    /**
     * R REPL (read-evaluate-parse loop) handler.
     */
    private static RMainLoopCallbacks rloopHandler = null;

    /**
     * SingletonHolder is loaded, and the static initializer executed, 
     * on the first execution of Singleton.getInstance() or the first 
     * access to SingletonHolder.INSTANCE, not before.
     */
    private static final class SingletonHolder {

        /**
         * Singleton instance, with static initializer.
         */
        private static final RInterfaceHL INSTANCE = initRInterfaceHL();

        /**
         * Initialize RInterfaceHL singleton instance using rLoopHandler from
         * outer class.
         * 
         * @return RInterfaceHL instance
         */
        private static RInterfaceHL initRInterfaceHL() {
            try {
                return new RInterfaceHL(rloopHandler);
            } catch (REngineException e) {
                // a static initializer cannot throw exceptions
                // but it can throw an ExceptionInInitializerError
                throw new ExceptionInInitializerError(e);
            }
        }

        /**
         * Prevent instantiation.
         */
        private SingletonHolder() {
        }

        /**
         * Get singleton RInterfaceHL.
         * 
         * @return RInterfaceHL singleton.
         */
        public static RInterfaceHL getInstance() {
            return SingletonHolder.INSTANCE;
        }

    }

    /**
     * Return the singleton instance of RInterfaceHL. Only the first call to
     * this will establish the rloopHandler.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @return RInterfaceHL singleton instance
     * @throws REngineException
     *             if REngine cannot be created
     */
    public static RInterfaceHL getInstance(RMainLoopCallbacks rloopHandler)
            throws REngineException {
        RInterfaceHL.rloopHandler = rloopHandler;

        RInterfaceHL instance = null;

        try {
            instance = SingletonHolder.getInstance();
        } catch (ExceptionInInitializerError e) {

            // rethrow exception that occurred in the initializer
            // so our caller can deal with it
            Throwable exceptionInInit = e.getCause();
            throw new REngineException(null, exceptionInInit.getMessage());
        }

        return instance;
    }

    /**
     * org.rosuda.REngine.REngine high level R interface.
     */
    private REngine rosudaEngine = null;

    /**
     * Construct new RInterfaceHL. Only ever gets called once by
     * {@link SingletonHolder.initRInterfaceHL}.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @throws REngineException
     *             if R cannot be loaded.
     */
    private RInterfaceHL(RMainLoopCallbacks rloopHandler)
            throws REngineException {

        // tell Rengine code not to die if it can't
        // load the JRI native DLLs. This allows
        // us to catch the UnsatisfiedLinkError
        // ourselves
        System.setProperty("jri.ignore.ule", "yes");

        rosudaEngine = new JRIEngine(new String[] { "--no-save" }, rloopHandler);
    }
}
于 2010-08-22T23:05:53.750 回答
4

如果你想创建一个作为 Context 的 Singleton 类,一个好方法是拥有一个配置文件并从 instance() 中的文件中读取参数。

如果为 Singleton 类提供的参数是在程序运行期间动态获取的,只需使用静态 HashMap 在 Singleton 类中存储不同的实例,以确保为每个参数创建一个实例。

于 2017-06-11T10:39:30.107 回答
3

您无法理解如何完成您正在尝试做的事情的原因可能是您正在尝试做的事情并没有真正的意义。你想getInstance(x)用不同的参数调用,但总是返回相同的对象?当你打电话时你想要什么行为getInstance(2)然后getInstance(5)

如果你想要同一个对象但它的内部值不同,这是它仍然是单例的唯一方式,那么你根本不需要关心构造函数;您只需在getInstance()对象退出时设置值。当然,您知道您对单例的所有其他引用现在都具有不同的内部值。

另一方面,如果您想返回不同的对象,则不是使用单例模式,而是使用工厂getInstance(2)模式。getInstance(5)

于 2009-06-26T20:07:56.393 回答
3

在您的示例中,您没有使用单例。请注意,如果您执行以下操作(假设 Singleton.getInstance 实际上是静态的):

Singleton obj1 = Singleton.getInstance(3);
Singleton obj2 = Singleton.getInstance(4);

那么 obj2.x 的值为 3,而不是 4。如果您需要这样做,请将其设为普通类。如果值的数量很小且固定,则可以考虑使用enum. 如果您遇到过多的对象生成问题(通常不是这种情况),那么您可以考虑缓存值(并检查源或获得帮助,因为很明显如何构建缓存而不存在内存泄漏的危险)。

您可能还想阅读这篇文章,因为单例很容易被过度使用。

于 2009-06-26T20:15:27.203 回答
3

单例是反模式的另一个原因是,如果根据建议编写,使用私有构造函数,它们很难子类化和配置以在某些单元测试中使用。例如,在维护遗留代码时需要。

于 2010-10-06T15:25:43.610 回答
1

这不是一个单例,但可能可以解决您的问题。

public class KamilManager {

  private static KamilManager sharedInstance;

  /**
   * This method cannot be called before calling KamilManager constructor or else
   * it will bomb out.
   * @return
   */
  public static KamilManager getInstanceAfterInitialized() {
    if(sharedInstance == null)
        throw new RuntimeException("You must instantiate KamilManager once, before calling this method");

    return sharedInstance;
}

  public KamilManager(Context context, KamilConfig KamilConfig) {
    //Set whatever you need to set here then call:
  s  haredInstance = this;
  }
}
于 2014-11-28T21:25:33.987 回答
1

我们不能做这样的事情:

public class Singleton {

    private int x;

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(int x) {
        Singleton instance = SingletonHolder.INSTANCE;
        instance.x = x;
        return instance;
    }
}
于 2015-12-04T12:38:30.057 回答
1

我害怕将此作为答案发布,但我不明白为什么没有人考虑这个问题,也许这个答案也已经给出了,我只是不明白。

public class example  {
    private volatile static example instance;

    private String string;
    private int iInt = -1; //any number you know you don't want to use here

  private example() {

    //In case someone uses the private method to create a new Instance
    if (instance != null){
      throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
    }
  }

  public synchronized static example getIsntance(){
    if(instance == null){
      instance = new example();
    }
    return instance;
  }

public void methodDoingWork(){
    if(checkInit()){
      //DoSome
    }
  }

  private boolean checkInit(){
    boolean filled = (this.string != null) && (this.iInt != -1);
    return filled;
  }

  public void setString(String string) {
    if(this.string == null){
      this.string = string;
    }else{
      throw new RuntimeException("You try to override an already setValue"); 
    }
  }

  public void setiInt(int iInt) {
    if(this.iInt == -1){
      this.iInt = iInt;
    }else{
      throw new RuntimeException("You try to override an already setValue");
    }
  }
}

由于getInstance()每次返回相同的实例,我认为这可以工作。如果这是错误的,我会删除它,我只是对这个话题感兴趣。

于 2019-08-30T07:45:00.777 回答
0

如果我们把问题理解为“如何用状态制作单例”,那么就没有必要将状态作为构造函数参数传递。我同意在获取单例实例后初始化状态或使用 set 方法的帖子。

另一个问题是:单例状态是否好?

于 2015-03-08T16:14:49.463 回答
0

尽管有些人可能会断言,这是一个在构造函数中带有参数的单例

public class Singleton {

    private static String aParameterStored;

    private static final Singleton instance = new Singleton("Param to set");

    private Singleton() {
        // do nothing
    }

    private Singleton(String param) {
        aParameterStored = param;
    }

    public static Singleton getInstance() {
        return instance;
    }

    /*
     * ... stuff you would like the singleton do
     */
}

单例模式说:

  • 确保单例类只存在一个实例
  • 提供对该实例的全局访问。

在这个例子中受到尊重。

为什么不直接设置属性?这是一个教科书案例,展示了我们如何获得一个具有带参数的构造函数的单例,但它在某些情况下可能很有用。例如在继承情况下强制单例设置一些超类属性。

于 2017-02-01T16:00:57.610 回答
0

要添加的东西,如果希望参数只应该初始化一次并且不能被覆盖,那么只需执行一个检查并在有人尝试再次初始化它们时抛出异常。前任。:

public class Service {

private String host = null;
private String port = null;

private Service() {
}

private static class ServiceSingletonHolder {

    private static final Service INSTANCE = new Service();
}

public static Service getInstance() {
    return ServiceSingletonHolder.INSTANCE;
}

public void initilize(String host, String port) {
    if (this.host != null && host != null) {
        throw new IllegalArgumentException("host can not be overwritten");
    }

    if (this.port != null && port != null) {
        throw new IllegalArgumentException("port can not be overwritten");
    }

    this.host = host;
    this.port = port;
}
}
于 2021-07-19T17:59:56.893 回答
-1

我认为这是一个普遍的问题。将单例的“初始化”与单例的“获取”分开可能会起作用(此示例使用双重检查锁定的变体)。

public class MySingleton {

    private static volatile MySingleton INSTANCE;

    @SuppressWarnings("UnusedAssignment")
    public static void initialize(
            final SomeDependency someDependency) {

        MySingleton result = INSTANCE;

        if (result != null) {
            throw new IllegalStateException("The singleton has already "
                    + "been initialized.");
        }

        synchronized (MySingleton.class) {
            result = INSTANCE;

            if (result == null) {
                INSTANCE = result = new MySingleton(someDependency);
            } 
        }
    }

    public static MySingleton get() {
        MySingleton  result = INSTANCE;

        if (result == null) {
            throw new IllegalStateException("The singleton has not been "
                    + "initialized. You must call initialize(...) before "
                    + "calling get()");
        }

       return result;
    }

    ...
}
于 2018-03-29T17:46:33.847 回答
-3

当然,单例是一种“反模式”(假设定义了具有可变状态的静态)。

如果您想要一组固定的不可变值对象,那么枚举就是要走的路。对于大量的、可能是开放式的值集,您可以使用某种形式的存储库 - 通常基于Map实现。当然,当你处理静态时要小心线程(要么同步足够广泛,要么使用ConcurrentMap检查另一个线程没有打败你,或者使用某种形式的期货)。

于 2009-06-26T20:16:03.817 回答
-4

单例通常被认为是反模式,不应使用。它们不会使代码易于测试。

无论如何,带有参数的单例是没有意义的——如果你这样写会发生什么:

Singleton s = SingletonHolder.getInstance(1);
Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException

您的单例也不是线程安全的,因为多个线程可以同时调用getInstance导致创建多个实例(可能具有不同的 值x)。

于 2009-06-26T20:05:30.440 回答