为什么有java单例类? 你什么时候需要使用一个

我理解单例类只能有一个实例,但我不明白为什么这样有用。 为什么不只是使用静态变量和方法创建一个类,并在需要时使用synchronize以确保没有两个线程同时在类中执行方法。 我只是不明白为什么有人会经历创造这种类的麻烦。 我知道我在这里遗漏了一些东西。

谢谢,

虽然我同意其他答案,但是OP问为什么没有一个带有所有静态方法的类(可能带有静态字段)而不是一个有一个实例的单例。

为什么要使用单身人士?

你可以谷歌“单身”找到各种各样的理由。 来自JavaWorld :

有时恰好只有一个类的实例:窗口管理器,打印假脱机程序和文件系统都是典型的例子。 通常,这些类型的对象(称为单例)在整个软件系统中由不同的对象访问,因此需要全局访问点。 当然,正当你确定你永远不会需要不止一个实例时,你可以改变主意。

为什么使用Singleton而不是使用所有静态方法的类?

有几个原因

  1. 你可以使用inheritance
  2. 您可以使用接口
  3. 它使得单例类本身的unit testing变得更容易
  4. 它可以对依赖于单例的代码进行unit testing

对于#3,如果您的Singleton是一个数据库连接池,您希望确保您的应用程序只有一个实例,但是在不命中数据库的情况下对数据库连接池进行unit testing(可能使用包范围构造函数或静态创作方法):

public class DatabaseConnectionPool { private static class SingletonHolder { public static DatabaseConnectionPool instance = new DatabaseConnectionPool( new MySqlStatementSupplier()); } private final Supplier statementSupplier; private DatabaseConnectionPool(Supplier statementSupplier) { this.statementSupplier = statementSupplier; } /* Visibile for testing */ static DatabaseConnectionPool createInstanceForTest(Supplier s) { return new DatabaseConnectionPool(s); } public static DatabaseConnectionPool getInstance() { return SingletonHolder.instance; } // more code here } 

(注意使用Initialization On Demand Holder模式)

然后,您可以使用package-scope createInstanceForTest方法测试DatabaseConnectionPool

但请注意,使用静态getInstance()方法可能会导致“静态绑定”,其中依赖于您的单例的代码无法进行unit testing。 由于这个原因, 静态单例通常不被认为是一种好习惯 (参见此博客文章 )

相反,您可以使用像Spring或Guice这样的dependency injection框架来确保您的类在生产中只有一个实例,同时仍然允许使用该类的代码是可测试的。 由于Singleton中的方法不是静态的,您可以使用像JMock这样的模拟框架来模拟测试中的单例。

只有静态方法(和私有构造函数)的类是一个根本没有实例(0个实例)的变体。

单例是一个只有1个实例的类。

这些是不同的东西,有不同的用例。 最重要的是国家 。 单身人士通常会保护对逻辑上只有一个人的访问。 例如,应用程序中的屏幕可能由单例表示。 创建单例时,初始化资源和与此事物的连接。

这与使用静态方法的实用程序类有很大区别 – 那里没有涉及的状态。 如果有,则必须检查(在同步块中)是否已创建状态,然后按需初始化它(懒惰)。 对于某些问题,这确实是一个解决方案,但您需要根据每个方法调用的开销来支付费用。

数据库实例是一个单例有用的地方,因为一个线程只需要一个数据库连接。 我敢打赌,还有很多其他的实例,比如数据库连接,你只想要一个东西的实例,这就是你要使用单例的地方。

对我来说,使用静态方法优先选择单例的原因是可测试性。 假设我确实需要确保一个且只有一个类的实例。 我可以使用单例或静态类只使用静态方法。 我们还要说我想在另一个类中使用这个类,但出于测试目的,我想嘲笑第一个类。 唯一的方法是将类的实例注入第二个类,并要求您具有非静态类。 在测试方面你仍然有些痛苦 – 你可能需要构建一些可以用reflection来调用的代码来删除单例以进行测试。 您也可以使用接口(虽然这将明确允许使用除另一个开发人员的单例以外的东西),并简单地将单例作为接口的实例提供给使用它的类。

一个考虑因素是将单例作为实例允许您实现接口。 仅仅因为你想控制它的实例化,并不意味着你希望每一段代码都知道它是一个单例。

例如,假设您有一个创建数据库连接的连接提供程序单例。

 public class DBConnectionProvider implements ConnectionProvider {} 

如果它是一个具有静态方法的类,则无法注入依赖项,如下所示:

 public void doSomeDatabaseAction(ConnectionProvider cp) { cp.createConnection().execute("DROP blah;"); } 

它必须是

 public void doSomeDatabaseAction() { DBConnectionProvider.createConnection().execute("DROP blah;"); } 

如果您以后想要对方法进行unit testing(您可以传入模拟的连接提供程序)以及其他内容,则dependency injection非常有用。

使用单例模式封装每个应用程序只应创建(初始化)一次的资源。 您通常对管理共享实体(如数据库)访问的资源执行此操作。 单例可以控制有多少并发线程可以访问该共享资源。 即因为有一个数据库连接池,它可以控制向需要它们的那些线程分发多​​少个数据库连接。 Logger是另一个示例,其中记录器确保可以适当地管理对共享资源(外部文件)的访问。 通常,单例也用于加载昂贵(慢)创建的资源。

你通常会创建一个像这样的单例,在getInstance上同步:

 public class Singleton { private static Singleton instance; private Singleton(){ // create resource here } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 

但创建它同样有效,

 public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){ // create resource here } public static Singleton getInstance() { return instance; } } 

这两种方法都将创建单个实例PER类加载器。

单例具有静态变量和方法的优势:它是一个对象实例,可以从一个类inheritance(例如:一个具有单个主JFrame的应用程序),并扩展一个或多个接口(因此被视为实现这些接口的任何其他对象)。

有些人认为单身人士是无用而危险的。 我并不完全同意,但有些观点是有效的,应该谨慎使用单身人士。

在我看来,用于代替单身人士的静态课程更加糟糕。 静态类应主要用于对不共享公共资源的相关函数进行分组。 java.util.Collections就是一个很好的例子。 它只是一堆与任何对象无关的函数。

另一方面,单身人士是真实的对象。 理想情况下,单例应该在没有单例模式的情况下实现。 如果您突然需要,这将允许您轻松切换到使用多个实例。 例如,您可能只有一个数据库连接,但是您必须同时使用另一个完全不相关的数据库。 你只需将你的单身人士变成“双人”或“三人”或其他什么。 如果需要,还可以将其构造函数设置为public,从而允许创建任意数量的实例。 所有这些东西都不容易用静态类实现。

简单地说,单例是一个常规类,它有一个全局可用的实例,以免您将其作为参数传递到任何地方。

创建单身人士并没有太大的麻烦。 您只需使用私有构造函数创建一个常规类,然后实现一个工厂方法,您就完成了。

你什么时候需要使用一个? 有许多对象我们只需要其中一个:线程池,高速缓存,对话框,处理首选项和注册表设置的对象,用于记录的对象,以及充当打印机和图形卡等设备的设备驱动程序的对象。 对于许多这些类型的对象,如果我们要多个实例化,我们会运行intol各种问题,如错误的程序行为或过度使用资源

关于同步的使用,它绝对是昂贵的,并且唯一的时间同步是第一次或唯一的实例一旦实例化,我们不再需要再次同步。 第一次通过后,完全不需要同步化:(

在我回复时,其他人提供了更好的答案,但我会留下我的答案给后人。 看来unit testing是这里的重要动力。

出于所有意图和目的,没有理由更喜欢单独使用您描述的方法。 有人认为静态变量是大写-B Bad(就像其他有时候很有用的function一样)因为它们离全球数据并不远,作为回应我们有单身人士的解决方法。

它们也与其他图案一起使用。 请参见可以将可观察的类构造为单例吗?