不使用单例模式有什么问题

What's wrong when not using singleton pattern

我正在从我的电子书(Head First Design Patterns)中阅读单例模式,我知道使用这种模式是合适的,以防您只需要某些 class.[=33 的一个实例=] 但是我对这本电子书中的问题介绍有点麻烦。
(是的,我想我可以在这里引用其中的一部分!?)

The Chocolate Factory
Everyone knows that all modern chocolate factories have computer controlled chocolate boilers. The job of the boiler is to take in chocolate and milk, bring them to a boil, and then pass them on to the next phase of making chocolate bars.
Here’s the controller class for Choc-O-Holic, Inc.’s industrial strength Chocolate Boiler. Check out the code; you’ll notice they’ve tried to be very careful to ensure that bad things don’t happen, like draining 500 gallons of unboiled mixture, or filling the boiler when it’s already full, or boiling an empty boiler!

public class ChocolateBoiler {
    private boolean empty;
    private boolean boiled;

    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    public void fill() {
        if (isEmpty()) {
            empty = false;
            boiled = false;
            // fi ll the boiler with a milk/chocolate mixture
        }
    }

    public void drain() {
        if (!isEmpty() && isBoiled()) {
            // drain the boiled milk and chocolate
            empty = true;
        }
    }

    public void boil() {
        if (!isEmpty() && !isBoiled()) {
            // bring the contents to a boil
            boiled = true;
        }
    }

    public boolean isEmpty() {
        return empty;
    }

    public boolean isBoiled() {
        return boiled;
    }
}

是的,这是他们的问题:

Choc-O-Holic has done a decent job of ensuring bad things don’t happen, don’t ya think? Then again, you probably suspect that if two ChocolateBoiler instances get loose, some very bad things can happen.
How might things go wrong if more than one instance of ChocolateBoiler is created in an application?

所以,当我们这样做时,问题会 "happen":

ChocolateBoiler boiler1 = new ChocolateBoiler(),
 boiler2 = new ChocolateBoiler();
//...

但是我看到这两个实例控制了自己的行为,并且它们运行独立(因为这里没有静态字段)。
所以它们运行分别对others.
我想知道这个问题是关于非法状态的,或者当一个实例 运行 对其他实例产生影响时可能会发生某些事情(“不正确的程序行为,过度使用资源,或 结果不一致”,来自电子书),但这里没有

所以,How might things go wrong here?,难道只是浪费实例吗?

if two ChocolateBoiler instances get loose, some very bad things can happen.

我想看看 bad things 是怎么发生的?

#编辑 1: 感谢大家帮助我。我弄清楚我的问题是什么,
当我调用 boiler2 = new ChocolateBoiler() 时,boiler2 实例仍然引用与 bolder1 相同的锅炉,是吗?
第一次觉得new ChocolateBoiler()和买个新锅炉差不多:)
这是构思,我是新手

使用单例模式的要点与Single Source of Truth principle和冲突管理相关(或可以视为其变体)。 单一事实来源当然也是一种冲突管理

单例模式的另一个方面是出于效率原因需要尽量减少不必要的重复and/or(重新)初始化。

例如,两个独立的实例会在同一个资源上发生冲突,这取决于所使用的应用程序和平台(例如多线程)可能会导致各种问题,例如死锁、无效状态等。

资源是一个(单例),因此该资源的管理器或驱动程序需要考虑到这一点,以避免对同一资源的潜在冲突(见上文)。

你似乎没有理解这个例子试图解释的概念。 ChocolateBoiler 不是真正的锅炉,它是 java class.

但是,此 class 可用于指示硬件(真正的锅炉控制器)执行某些操作。如果您错误地拥有两个 ChocolateBoiler 实例,并且错误地使用它们来指示同一个锅炉控制器,那么显然您有麻烦了。

在我的上一段中有两个 'mistakenly',您可能会争辩说,如果您做的事情 'mistakenly' 一般,那么您无论如何都会遇到麻烦。但如果是设计糟糕的单例,错误可能不会那么明显。如果您序列化和反序列化一个不处理序列化问题以保持唯一性的单例,然后尝试使用该实例来加热锅炉,您可能会烧毁锅炉。

您应该注意单例模式的几个问题。 让我们考虑两个不同的单例示例:

1) 无状态单例

此单例将没有字段成员,只会将方法作为服务提供给外界。

public class StatelessSingleton {

    private static final StatelessSingleton INSTANCE = new StatelessSingleton();

    private StatelessSingleton() {

        // exists to defeat instantiation
    }


    public void service() {
        //...
    }

    public void anotherService() {
        //..
    }

    public StatelessSingleton getInstance() {
        return INSTANCE;
    }
}

出于性能和可读性的原因,这种类型的单例通常最好用只有静态方法的 class 代替。例外情况可能是在实现诸如 Strategy 之类的模式时,您需要将某些接口实现为无状态算法,因为缓存此实现很有意义。要实现接口,你显然需要一个实例。

2) StatefullSingletion

public class StatefullSingleton {

    private int a = 3;

    private static final StatefullSingleton INSTANCE = new StatefullSingleton();

    private StatefullSingleton() {

        // exists to defeat instantiation
    }


    public void service() {
        // do some write operation on a
    }

    public void anotherService() {
        // do some read operation on a
    }

    public StatefullSingleton getInstance() {
        return INSTANCE;
    }

}

现在,关于单例的问题:

如果实施不当,这两种单例都可能导致不止一个实例。如果您在 java 中使用双重检查锁定以确保仅存在一个 Singleton 实例,则会发生这种情况:

class Foo {
    private Helper helper;
    public Helper getHelper() {
        if (helper == null) {
            helper = new Helper();
        }
        return helper;
    }

    // other functions and members...
}

有大量可用资源讨论为什么这在 java 中不起作用,因此无需在此重复这一点。

避免双重检查锁定问题的一种方法如上所示,使用私有构造函数和对实例的静态引用 + getter.

第二个问题,特定于 StatefullSingelton,如果您的服务方法不同步,多个线程可能会弄乱此类 Singelton 的状态。在你的例子中,如果不同的工人同时给锅炉加水和排水,可能会出问题。

第三期是连载。鉴于 Singleton 实现了 java.io.Serializable 接口,这可能导致在反序列化期间有多个 Singleton。为避免在反序列化时创建新对象,必须实现 readResolve