什么是单例模式
单例模式是一种常用的软件设计模式。它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。
单例模式的优点
- 实例控制 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
- 灵活性 因为类控制了实例化过程,所以类可以灵活更改实例化过程。
- 减少内存开销
- 提供了对唯一实例的受控访问
- 允许可变数目的实例
- 避免资源多重占用
单例模式的缺点
- 没有抽象层,因此单例类的拓展性差
- 不适用于变化的对象
- 违背了单一职责
应用场景
需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
单例模式写法1
饿汉式: 使用静态常量方式创建实例
public class SingletonDemo1 {
/**
* 1. 私有构造方法
*/
private SingletonDemo1() {
}
/**
* 2. 类内部之间创建对象
*/
private static final SingletonDemo1 SINGLETON_DEMO_1 = new SingletonDemo1();
/**
* 3. 提供公有方法,获取实例
*/
public static SingletonDemo1 getInstance() {
return SINGLETON_DEMO_1;
}
}
测试
public static void main(String[] args) {
SingletonDemo1 instance1 = SingletonDemo1.getInstance();
SingletonDemo1 instance2 = SingletonDemo1.getInstance();
System.out.println(instance1==instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
输出结果
true
1804094807
1804094807
Process finished with exit code 0
优点: 写法简单,类加载时就完成实例化,避免线程同步问题.
缺点: 类加载时完成初始化,没有懒加载.如果从始至终没有用到这个类,会造成 内存浪费.
总结: 在不考虑内存浪费的情况下还是可以使用的,毕竟现在的环境也不缺少这点内存
单例模式写法2
饿汉式: 使用静态代码块实例对象
public class SingletonDemo2 {
/**
* 1. 私有构造方法
*/
private SingletonDemo2() {
}
private static SingletonDemo2 singletonDemo2;
/**
* 2. 创建实例
*/
static {
singletonDemo2 = new SingletonDemo2();
}
/**
* 3. 提供公有方法获取实例
*/
public static SingletonDemo2 getInstance() {
return singletonDemo2;
}
}
优点: 这种方式和第一种类似。写法简单,类加载时就完成实例化,避免线程同步问题.
缺点: 类加载时完成初始化,没有懒加载.如果从始至终没有用到这个类,会造成内存浪费.
总结: 在不考虑内存浪费的情况下可以使用.
单例模式写法3
懒汉式: 线程不安全
public class SingletonDemo3 {
/**
* 1. 私有构造方法
*/
private SingletonDemo3() {
}
private static SingletonDemo3 singletonDemo3;
/**
* 2. 提供获取实例的公有方法,使用该方法时才会创建实例
*/
public static SingletonDemo3 getInstance() {
if (null == singletonDemo3) {
singletonDemo3 = new SingletonDemo3();
}
return singletonDemo3;
}
}
优点: 起到懒加载效果,节省内存
缺点: 只能在单线程中使用.多线程下如果线程1进入 if 语句中还未完成初始化,线程2又进入 if 中,会产生多个实例.在实际开发中不要使用这种方式.
总结: 不推荐使用
单例模式写法4
懒汉式: 同步方法
public class SingletonDemo4 {
/**
* 1. 私有构造方法
*/
private SingletonDemo4() {
}
private static SingletonDemo4 singletonDemo4;
/**
* 2. 提供获取实例的公有方法,加入同步代码块,解决线程不安全问题,使用该方法时才会创建实例.
*/
public static synchronized SingletonDemo4 getInstance() {
if (null == singletonDemo4) {
singletonDemo4 = new SingletonDemo4();
}
return singletonDemo4;
}
}
优点: 解决线程不安全问题
缺点: 效率低,每个线程获取实例时,都要进行同步
总结: 不推荐使用,效率太慢
单例模式错误写法
贴出错误代码的原因是如果看到这种写法,能发现错误
public class SingletonDemo5 {
/**
* 1. 私有构造方法
*/
private SingletonDemo5() {
}
private static SingletonDemo5 singletonDemo5;
/**
* 2. 提供获取实例的公有方法,加入同步代码块,然而并没有什么用,线程依然不安全
*/
public static SingletonDemo5 getInstance() {
if (null == singletonDemo5) {
// 注意这里
synchronized (SingletonDemo5.class) {
singletonDemo5 = new SingletonDemo5();
}
}
return singletonDemo5;
}
}
总结: 看似聪明的加了锁,实则卵用没有. 多个线程进入 if 中, 依然产生不同实例
单例模式写法6
懒加载: 双重检查枷锁、volatile关键字修饰
public class SingletonDemo6 {
/**
* 1. 私有构造方法
*/
private SingletonDemo6() {
}
/**
* 2. volatile 关键字修饰
*/
private static volatile SingletonDemo6 singletonDemo6;
/**
* 3. 提供公共的实例方法,使用双重检查实例对象
*/
public SingletonDemo6 getInstance() {
if (null == singletonDemo6) {
synchronized (SingletonDemo6.class) {
if (null == singletonDemo6) {
singletonDemo6 = new SingletonDemo6();
}
}
}
return singletonDemo6;
}
}
优点: 线程安全,延迟加载,同时保证效率 推荐使用
缺点: 写法稍微复杂
总结: volatile 关键字防止指令重排序问题,立即更新到主存.保证一个线程更新instance 其余线程立即可知
类的初始化字节码层步骤:
- 分配空间
- 初始化
- 引用赋值
JIT、CPU 等会对指令重排: 第2步和第3步顺序随机颠倒,在单线程下对执行结果没有影响
多线程下:
线程1走到 if (null == singletonDemo6) 为空时继续往下走、进锁、发现对象为空开始初始化
如果按照 分配空间、引用赋值、初始化 的顺序执行,假设线程1走到引用赋值时, 线程2进入 getInstance() 方法,发现 singletonDemo6 有值,直接返回实例. 但线程1此时还没有执行 初始化 步骤,将有可能会发生空指针等问题
因此加入 volatile 关键字防止指令重排
单例模式写法7
使用静态内部类实例对象
public class SingletonDemo7 {
/**
* 1. 私有化构造参数
*/
private SingletonDemo7() {
}
/**
* 2. 通过静态内部类的方式实例对象
*/
private static class SingletonDemo7Instance {
private static final SingletonDemo7 SINGLETON_DEMO_7 = new SingletonDemo7();
}
/**
* 3. 提供对外的实例方法
*/
public static SingletonDemo7 getInstance() {
return SingletonDemo7Instance.SINGLETON_DEMO_7;
}
}
优点: 采用类装载机制来保证初始化实例时只有一个线程. 利用静态内部类的特点实现懒加载,效率高 推荐使用
缺点: 写法稍微微有点点复杂
总结: 静态内部类特点
- 当SingletonDemo7装载的时候,静态内部类不会装载
- 当使用getInstance()时,静态内部类才开始装载,并且只会被装载一次
- 当类被装载的时候,线程是安全的
单例模式写法8
枚举实现
public enum SingletonDemo8 {
INSTANCE
}
测试
public static void main(String[] args) {
SingletonDemo8 instance1 = SingletonDemo8.INSTANCE;
SingletonDemo8 instance2 = SingletonDemo8.INSTANCE;
System.out.println(instance1 == instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
结果
true
1804094807
1804094807
Process finished with exit code 0
优点: 利用枚举的特性,不仅避免了多线程安全的问题,而且还防止反序列化重新创建新的对象. 并且这种方式是写法简单,Effective作者Josh Bloch 提倡的方式
缺点: 你猜
总结: 推荐使用