单例模式-序列号生成器

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. 单例模式的定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式属于创建型模式,它的核心操作是:

  1. 私有化构造器,防止其他类通过new关键字创建实例。
  2. 类内部自行实例化,并用静态常量保存(也可延迟实例化)。
  3. 提供一个公开的静态方法来访问这个实例。

也可以将静态常量声明为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. 单例模式的优缺点

优点

  1. 减少了内存的开支,因为单例模式在内存中只有一个实例。
  2. 减少了系统的性能开销,如果一个对象需要被频繁的创建和销毁,使用单例永久驻留在内存会很有优势。
  3. 避免对资源的多重占用,例如写文件。

缺点

  1. 扩展困难,单例模式一般没有接口,因为它要求类“自行实例化”。若要扩展,只能修改代码。
  2. 与单一职责冲突,单例类既要负责业务逻辑,又要保证是单例。

5. 总结

单例模式算是23种设计模式中最简单的一个了,它要求类自行实例化,并向全局提供这个实例。单例模式应用非常的广泛,例如Spring的IOC容器中,每个Bean默认就是单例的,JDK的java.lang.Runtime就是用【饿汉式】实现的单例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小潘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值