Java 中具有参数的单例

2022-08-31 07:18:43

我正在阅读维基百科上的辛格尔顿文章,我遇到了这个例子:

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

虽然我真的很喜欢这个单例的行为方式,但我看不出如何调整它以将参数合并到构造函数中。在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;
    }
}

谢谢!


编辑:我认为我已经开始了一场争议风暴,我想要使用辛格尔顿。让我解释一下我的动机,希望有人能提出一个更好的主意。我正在使用网格计算框架来并行执行任务。一般来说,我有这样的东西:

// 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 类。我不会再举另一个例子,但我希望你明白这个想法。请让我听听你的想法,以一种更优雅的方式来完成我想要做的事情。再次感谢!


答案 1

我将非常清楚地说明我的观点:具有参数的单例不是单例

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

您有两种选择。如果您希望使用某些数据初始化单例,则可以在实例化后将其与数据一起加载,如下所示:

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

在任何情况下,实例化将始终是无参数的。否则,您的单例不是单例。


答案 2

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

尽管您可能会达到应该使用常规非单例类的地步(例如,需要 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 s也可以被认为是(或用作)参数化的单例,尽管只允许固定数量的静态变体。enum

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

1 表示跨越可能多台计算机上的多个 VM。