1 定义:
1.1文字定义:Ensure a classhas only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)
1.2通用类图:

图1 单例模式通用类图
1.3通用代码:
public class Singleton {
privatestatic final Singleton singleton = new Singleton();
//限制产生多个对象
privateSingleton(){
}
//通过该方法获得实例对象
publicstatic Singleton getSingleton(){
returnsingleton;
}
//类中其他方法,尽量是static
publicstatic void doSomething(){
}
}2优点:
- 内存仅有一个实例,减少内存开支;特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
- 当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永驻留内存的方式来解决(在Java EE中注意JVM垃圾回收机制)。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
3缺点:
3.1 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有别的途径。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例。接口、抽象类是不可能被实例化的。当然特殊情况下,单例模式可以实现接口、被继承等,需要在系统开中根据环境判断。
3.2 单例模式对测试不利。在并行开发环境中,如果单例没有完成,是不能进行测试的。
3.3 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。
4使用场景:
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体场景如下:
- 要求生成唯一序列号的环境;
- 在整个项目中需要一个共享访问点或共享数据,如计数器;
- 创建一个对象需要消耗资源过多,如要访问IO和数据库等资源;
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式。
5注意事项:
5.1 高并发时注意单例模式的线程同步问题。
在单例的实例化时,有“饿汉式”如1.3中实例化方法;也有使用同步方法,仅在需要时创建的方式,这种方式称为“懒汉式”单例;后者不是优秀的方式,前者为推荐方式。
若后者未添加同步关键字,则可能导致“创建 赋值”过程中,被另一调用再次创建。
5.2 考虑对象复制情况。
在Java中,对象默认是不可以被复制的,若实现了Cloneable接口,并实现了clone方法,则可以直接通过对象复制方式创建一个新对象,对象复制不调用类的构造函数,因此即使是私有的构造函数,对象仍然可以被复制。即,单例类不要实现Cloneable接口。
6扩展:
如果一个类只要一个对象,使用单例模式即可;如果一个类可以只要两个、三个对象呢?亦可参照单例模式,静态初始若干实例,然后使用时或随机或按某种规则取出即可。
7范例:
单例用法(计数器类,部分工具类)
package _01_Singleton;
public class Counter {
private static final Counter counter = new Counter();//创建实例
private static int total = 0;
private Counter(){}
public static Counter getInstance(){//获取实例
return counter;
}
//做点事。
public synchronized void Increase(){
total++;
}
//做点事。
public synchronized int getTotal(){
return total;
}
//工具类可参考
public static void whatAmI(){
System.out.println("我是计数器工具方法");
}
}
测试:
package _01_Singleton;
public class TestCounter {
public static void main(String[] args) {
Counter.whatAmI();
Counter c = Counter.getInstance();
for(int i=0; i<10; i++){
c.Increase();
System.out.println("The current total is : " + c.getTotal());
}
}
}
结果:
我是计数器工具方法
The current total is : 1
The current total is : 2
The current total is : 3
The current total is : 4
The current total is : 5
The current total is : 6
The current total is : 7
The current total is : 8
The current total is : 9
The current total is : 10
注意:
使用单例模式需要注意的一点就是JVM的垃圾回收机制,如果我们的一个单例对象在内存中长久不使用,JVM就认为这个对象是一个垃圾,在CPU资源空闲的情况下,该对象会被清理掉,下次再调用时就需要重新产生一个对象。如果我们在应用中使用单例类作为有状态值(如计数器)的管理,则会出现恢复原状的情况,应用就会出现故障。如果确实需要采用单例模式来记录有状态的值,有两种方法可以解决该问题:(下述为转述,未尝试)
由容器管理单例的生命周期
JavaEE容器或者框架级容器(如Spring)可以让对象长久驻留内存。当然,自行通过管理对象的生命期也是一个可行的办法,既然有那么多的工具提供我们,为什么不用呢?
状态随时记录
可以使用异步记录的方式,或者使用观察者模式,记录状态的变化,写入文件或写入数据库中,确保即使单例对象重新初始化也可以从资源环境获得销毁前的数据,避免应用数据丢失。
本文详细介绍了单例模式的概念、优缺点及其应用场景。通过代码示例展示了如何实现单例模式,并讨论了其在并发环境下的线程同步问题及对象复制情况。
1031





