Java中带有参数的Singleton


142

我正在阅读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).
    }
}

如您所见,即使在这里,我仍然遇到一个问题,即传递第一个文件后,传递不同的文件路径没有任何意义。这就是为什么我喜欢答案中张贴的商店的想法。无论如何,我想将逻辑抽象为Singleton类,而不是在run方法中包括用于加载文件的逻辑。我将不再提供另一个示例,但是希望您能理解。请让我听听您的想法,以更优雅的方式完成我尝试做的事情。再次感谢你!


1
您想要的是工厂模式。理想情况下,网格任务应完全独立于其他任何事物,并向其发送执行和返回结果所需的所有数据。但是,这并不总是最可行的解决方案,因此将数据序列化到文件并不是一个坏主意。我认为整个单例的事情有点像是一条红鲱鱼。你不想单身。
oxbow_lakes

2
不幸的是,您使用了此类行李附带的术语Singleton。实际上,此模式的恰当术语是Interning。实习是一种确保抽象值仅由一个实例表示的方法。字符串实习是最常见的用法:en.wikipedia.org/wiki/String_intern_pool。
notnoop

您可能想看看兵马俑。它在整个集群中维护对象身份。当您发送对集群中已有数据的引用时,它不会重新序列化。
Taylor Gautier

21
抛开是否应该使用单例模式的问题,我要指出的是,几乎每个答案似乎都假设提供参数的目的是允许创建“多个单例”,并通过值来区分。所述参数。但是,另一种可能的用途是提供访问到的是一个外部对象同类型的单例类的对象唯一实例永远都需要。因此,我们需要将为此类访问提供的参数与旨在创建“多个单例实例”的参数区分开。
卡尔

2
“带有参数的单例”的另一种情况:一个Web应用程序,它将基于第一个即将到来的请求(线程)附带的信息来构建其唯一的不变单例。请求的域可以确定某些单例行为,例如
fustaki

Answers:


171

我会很清楚地指出:具有参数的单例不是单例

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

您有两个选择。如果您希望用一些数据初始化单例,则可以在实例化之后用数据加载它,如下所示:

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

无论如何,实例化总是没有参数的。否则您的单身人士不是单身人士。


1
+1这就是我在编码时可能会做的事情。在C#中,我只是使用属性。Java,大概是这样的。
扎克

131
对不起,那不是真的。在某些情况下,您必须传递动态创建的参数,这些参数对于Hole应用程序运行时保持不变。因此您不能在单例中使用常量,而必须在创建常量时传递该常量。经过一次后,其相同的常数为空洞时间。如果您需要在构造函数中使用该特定常量,则setter将不会执行此工作。
masi

1
正如作者所说,@ masi不是一个单例。如果需要动态地传递常量,则可能需要创建许多带有不同常量的此类。因此,单例没有意义。
德米特里·扎耶采夫

53
如果在应用程序的整个生命周期中只需要一个类的实例,但是需要在启动时为该实例提供一个值,为什么它不再是单例呢?
奥斯卡

4
“如果试图将参数提供给构造函数,那么单例的意义是什么?” -也可能会说:“如果将整个应用程序设为单个实例,那么命令行参数的意义是什么?”,答案是,这样做很有意义。现在可以说这与单例类完全不同,除非该类实际上是确实从main方法接收args []的Main类-甚至是同一回事。最后的论点可能会成立,那就是这是一个非常特殊的情况。
Dreamspace总裁

41

我认为您需要像工厂这样的东西来实例化和重用各种参数的对象。它可以通过使用同步对象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 enum的发展,尽管只允许使用固定数量的静态变量,但也可以将Java 视为(或用作)参数化的单例。

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

1可能跨多个计算机上的多个VM。


是的,这正是我所需要的。非常感谢你!我同意我在示例中处理参数的方式没有多大意义,但我没有想到这一点。请参阅oxbow_lakes答案的注释中的解释。

1
不是单例。您现在有不止一个。大声笑
oxbow_lakes

@斯科特:我建议像尤瓦尔建议如下。这更有意义,并且您有一个“真正的”单身人士。编辑
扎克

我希望没有人介意我在代码中编辑名称。我可以想象这对于新手来说真的很令人困惑。如果您不同意,请回滚
oxbow_lakes

是的,我们可以称其为Multitron,但仍然可以达到IM最初希望OP达到的相同目标。
akarnokd

22

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

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)一次进行配置,例如在应用启动时。


13

如果要显示某些参数是必需的,也可以使用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();
}

6

带有参数的单例不是单例 ”语句并不完全正确。我们需要从应用程序的角度而不是从代码的角度进行分析。

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

设计模式可以帮助我们编写灵活且可扩展的代码,而不会妨碍我们编写良好的代码。


12
这不是OP问题的答案,应该是评论。
Thierry J.

5

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

Singleton.getInstance().setX(value);

1
不知道为什么这被否决了。.这是一个有效的答案。:/
Zack

13
因为这是垃圾的答案。例如,假设有一个系统,其中初始admin的初始用户名和密码是构造函数参数。现在,如果我将其做成一个单例并按照您所说的去做,我会得到管理员的getter和setter方法,这并不是您想要的。因此,尽管您的选择在某些情况下可能是有效的,但它并不能真正回答问题所在的一般情况。(是的,我正在我所描述的系统上工作,不,如果不是因为作业说“在这里使用单例模式”,我就不会使用单例模式)
Jasper

5

令人惊讶的是,没有人提到记录器是如何创建/检索的。例如,下面显示了如何检索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      } 
...

4

使用Bill Pugh的按需持有人惯用语初始化的Singleton模式的修改。这是线程安全的,而没有专用语言结构(即,易失性或同步)的开销:

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

我认为finally { RInterfaceHL.rloopHandler = null; }in 是个好主意getInstance,因为如果我们不小心,该静态引用可能会导致内存泄漏。在您的情况下,这似乎不是问题,但是我可以想象一个场景,其中传入的对象很大,并且仅由RInterfaceHLctor用于获取一些值,而不是对其进行引用。
TWiStErRob

想法:return SingletonHolder.INSTANCE在中可以正常使用getInstance。我认为这里不需要封装,因为外部类已经知道内部类的内部,它们紧密地耦合在一起:它rloopHandler在调用之前知道需要init。私有构造函数也没有作用,因为内部类的私有内容对于外部类是简单可用的。
TWiStErRob'6


3

您无法理解如何完成要尝试执行的操作的原因可能是您要尝试执行的操作实际上没有任何意义。您想getInstance(x)使用不同的参数进行调用,但始终返回相同的对象吗?是你要什么的行为,当你打电话getInstance(2),然后getInstance(5)

如果您希望同一个对象但其内部值不同(这是它仍然是单例的唯一方法),那么您根本就不需要关心构造函数。您只需将值设置为getInstance()对象的出路即可。当然,您了解所有其他对单例的引用现在具有不同的内部值。

如果你想getInstance(2)getInstance(5)返回不同的对象,在另一方面,你不使用Singleton模式,您使用的工厂模式。


3

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

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

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

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


3

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


3

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

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


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

1

如果我们将问题视为“如何使状态成为单例”,则不必将状态作为构造函数参数传递。我同意在获取单例实例后初始化状态或使用set方法的帖子。

另一个问题是:让单身人士与州在一起好吗?


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

1

尽管有人可能会断言,但这是构造函数中具有参数的单例

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
     */
}

单例模式说:

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

本示例对此予以尊重。

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


0

我不敢将其发布为答案,但是我不明白为什么没人考虑这个,也许这个答案也已经给出了,我只是不理解。

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()每次都返回相同的实例,所以我认为这可能有效。如果这在很大程度上是错误的,我将其删除,我只是对该主题感兴趣。


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

    ...
}

我想总是可以在initialize方法中返回“结果”。
迈克尔·安德鲁斯

-2

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

如果您需要一组固定的不可变值对象,则可以使用枚举。对于大型的,可能是开放式的值集,可以使用某种形式的存储库-通常基于Map实现。当然,在处理静态变量时,请谨慎使用线程(要么进行足够广泛的同步,要么使用ConcurrentMap另一种方法检查是否没有击败您,或者使用某种形式的期货)。


4
仅当反模式使用不正确时,尽管这是反模式的定义。仅仅因为您看到他们过去不属于他们,并不意味着他们没有地方。
geowa4

单例的正确用法是演示不称职的代码。
汤姆·霍顿-大头钉

-6

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

无论如何,带有参数的单例毫无意义-如果您这样写,将会发生什么:

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

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


这是有争议的。
AlbertoPL

1
是的,这值得商;;因此,我通常使用“一般”一词。我认为可以公平地说,它们通常被认为是一个坏主意
oxbow_lakes

这值得商--有人声称所谓的“反模式”符合模式的定义,只是它们是不良模式。
Tom Hawtin-大头钉

我了解他们很糟糕。我正在做分布式计算,需要在多个任务之间共享一个对象。与其确定性地初始化静态变量,不如将逻辑抽象为Singleton。我想我可以使getInstance同步。这行得通吗?我需要做的是为多个任务加载一次文件,并且仅在发送第一个任务之后才加载文件。(我不想序列化我的数据。)我想我可以将AbstractFileReader作为getInstance方法的参数,以使Singleton更灵活。我很重视您的意见。

我认为您可能会误解“分布式”的含义?还有其他方法可以实现您想要的目标:您是否考虑过依赖注入?还是JNDI?
oxbow_lakes
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.