1. 序列号生成器
设计一个全局的序列号生成器,返回long类型,要求生成的序列号全局唯一且递增。
序列号的实现很简单,如下:
class SerialGenerator{
private long code;
// 返回下一个号码
public synchronized long next(){
return ++code;
}
}
如果不是单例,多个生成器实例生成的序列号都是从0开始,会造成冲突,优化如下:
class SerialGenerator{
private long code;
private static final SerialGenerator INSTANCE = new SerialGenerator();
// 私有化构造器,防止外部new
private SerialGenerator(){}
public synchronized long next(){
return ++code;
}
public static SerialGenerator getInstance(){
return INSTANCE;
}
}
客户端调用:
public class Client {
public static void main(String[] args) {
SerialGenerator serialGenerator = SerialGenerator.getInstance();
serialGenerator.next();
}
}
这就是单例模式。
2. 单例模式的定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式属于创建型模式,它的核心操作是:
- 私有化构造器,防止其他类通过
new
关键字创建实例。 - 类内部自行实例化,并用静态常量保存(也可延迟实例化)。
- 提供一个公开的静态方法来访问这个实例。
也可以将静态常量声明为public
,外部类直接访问单例,但是不建议这么做,调用getInstance()
方法来获取实例,方便以后扩展。
3. 单例模式的扩展
上面写的例子属于【饿汉式】,不管客户端是否需要访问实例,只要Class文件被Load到内存,实例就会被创建。
3.1 DCL懒汉式
DCL全称是:Double Check Lock,双重检查加锁,是【懒汉式】的一种实现方式。实例默认不会创建,只有当客户端真正需要访问实例时,实例才会被创建,重点是控制并发访问。
public class SerialGenerator_DCL {
private long code;
private static volatile SerialGenerator_DCL instance;
public synchronized long next() {
return ++code;
}
public static SerialGenerator_DCL getInstance() {
if (instance == null) {
synchronized (SerialGenerator_DCL.class) {
if (instance == null) {// recheck
instance = new SerialGenerator_DCL();
}
}
}
return instance;
}
}
3.2 内部类实现单例
【懒汉式】的另外一种实现方式,通过内部类来持有实例对象,当单例类被Load时,内部类并不会被加载,也就不会创建实例,只有当客户端真正需要访问实例时,内部类才会被加载并创建实例。
public class SerialGenerator_InnerClass {
private long code;
private SerialGenerator_InnerClass() {
}
public synchronized long next() {
return ++code;
}
private static class InnerClass {
final static SerialGenerator_InnerClass INSTANCE = new SerialGenerator_InnerClass();
}
public static SerialGenerator_InnerClass getInstance() {
return InnerClass.INSTANCE;
}
}
3.3 单例枚举
《Effective Java》的作者Joshua Bloch在《用私有构造器或者枚举类型强化Singleton属性》一节中提出了实现单例的另一种方式:包含单个元素的枚举类型。
前面写的几种单例实现方式,从严格意义上来说,是有“缺陷”的。通过Java的反射技术,可以调用私有构造器创建多个实例,另外,反序列化也会对单例造成破坏,具体可以查看笔者的博客:《你写的单例真的安全吗?》。
枚举的方式可以避免这种破坏,是实现单例的最佳方式:
public enum SerialGenerator_Enum {
INSTANCE;
private long code;
public synchronized long next() {
return ++code;
}
}
4. 单例模式的优缺点
优点
- 减少了内存的开支,因为单例模式在内存中只有一个实例。
- 减少了系统的性能开销,如果一个对象需要被频繁的创建和销毁,使用单例永久驻留在内存会很有优势。
- 避免对资源的多重占用,例如写文件。
缺点
- 扩展困难,单例模式一般没有接口,因为它要求类“自行实例化”。若要扩展,只能修改代码。
- 与单一职责冲突,单例类既要负责业务逻辑,又要保证是单例。
5. 总结
单例模式算是23种设计模式中最简单的一个了,它要求类自行实例化,并向全局提供这个实例。单例模式应用非常的广泛,例如Spring的IOC容器中,每个Bean默认就是单例的,JDK的java.lang.Runtime
就是用【饿汉式】实现的单例。