1. 介绍
单例模式(Singletion Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
单例类只能有一个实例
单例类必须自己创建自己的唯一实例
单例类必须给其他对象提供这一实例
2. 使用场景
要求生产唯一的序列号
WEB中的计数器,不用每次都去数据库里加一次,用单例缓存起来
创建一个对象需要消耗的资源过多,比如I/O与数据库的连接
spring中的单例Bean
优点:在内存中只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
避免对资源的多重占用
缺点:没有接口、不能继承 只关心内部逻辑,不考虑外部如何实例化
3. 实现方法
饿汉式 V1.0
当该类被加载到内存时,就会实例化一个单例,JVM 保证其线程安全
唯一缺点:不管能不能用到,类装载时就会完成实例化
这种方法 简单实用,推荐使用
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
private Mgr01() {
}
public static Mgr01 getInstance() {
return INSTANCE;
}
}
懒汉式 V1.0
只有我需要的时候,我才会去进行实例化,达到了按需初始化的目的
缺点:不支持多线程,因为没有加锁,在多线程状态下不能正常工作
public class Mgr03 {
private static Mgr03 INSTANCE;
private Mgr03() {
}
public static Mgr03 getInstance() {
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr03();
}
return INSTANCE;
}
懒汉式 V2.0
相较于 1.0 版本,在 2.0 版本中加入了 synchronized 关键字保证其实例化。
public class Mgr04 {
private static Mgr04 INSTANCE;
private Mgr04() {
}
public static synchronized Mgr04 getInstance() {
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr04();
}
return INSTANCE;
}
双端检验锁(DCL,既:double-checked locking)
主要为了解决上述懒汉式测试版本出现的无效问题将 synchronized 直接放在外面,里面加一个判断null不可以吗?可以是可以,但是在一般情况下,多个线程同时走到同一行代码的判断是比较少的当我们的某个线程已经创建了实例化,我们在外面加一个判断,就会筛过之后的线程,不需要进行锁的争夺
为什么用 volatile ?
Java 在进行编译的时候,为了使程序效率加快,会将没有相互联系的指令进行指令重排
对象在创建的时候,分为三个阶段
● 给 INSTANCE 分配堆内存
● 调用 Mgr06 的构造函数来初始化成员变量,形成实例
● 将 INSTANCE 指针指向分配的内存空间(执行完这步 INSTANCE 才是非 null了)
正常来讲:按照 1-2-3 的顺序是不会出错的,但是指令重排可能会出现 1-3-2 的情况,我们的对象还没有初始化成员变量,就已经分配好内存空间,造成数据的严重错误。
public class Mgr06 {
private volatile static Mgr06 INSTANCE;
private Mgr06() {
}
public static Mgr06 getInstance() {
if (INSTANCE == null) {
synchronized (Mgr06.class) {
try {
Thread.sleep(1);
} catch (Exception e) {
e.getStackTrace();
}
if (INSTANCE == null) {
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
静态内部类
JVM 保证单例,只加载一次
加载外部类时不会加载内部类,这样可以实现懒加载,真正的实现了按需加载的目的
public class Mgr07 {
private Mgr07(){
}
private static class Mgr07Handle{
private static final Mgr07 INSTANCE = new Mgr07();
}
public static Mgr07 getInstance(){
return Mgr07Handle.INSTANCE;
}
枚举
它不仅能避免多线程同步问题,还可以防止序列化和反序列化
枚举类没有构造方法,源码规定,在反射的时候,判断该类是否被ENUM修饰,如果是则直接抛出异常,反射失败
public enum Mgr08 {
INSTANCE;
public Mgr08 getInstance(){
return INSTANCE;
}
}