Java中几种单例模式的实现
个人博客
1、饿汉式
1 | public class Apple { |
以上为饿汉式单例模式简单实现,此种方式不会产生线程不安全情况,且只会创建一个对象。
饿汉式单例有以下特点
- 私有构造。
- 静态私有属性和静态公有方法。
2、懒汉式
2.1、非线程安全
1 | public class Banana { |
如果有多个线程调用getInstance
方法,都会进入if判断,从而造成重复实例化,不是真正的单例。
2.2、线程安全
1 | public class Banana { |
上面的方式,虽然解决了线程安全问题,但是如果在初始化完成之后,每次调用获取还要再经过同步锁,多线程环境下会降低程序运行的效率。
2.3、双重检查
1 | public class Banana { |
第一层检查是只有实例还未初始化才进入同步锁,如果已经初始化完成就直接返回,这样可以提升效率。
第二层检查是为了防止前一个线程实例化之后释放锁,后面多个线程串行再去实例化,所以要加个判断。
2.4、volatile禁止指令重排序
1 | public class Banana { |
因为apple = new Apple()
创建对象有以下3个步骤:
- 在堆上开辟空间。
- 调用构造器,初始化实例。
- 返回地址给引用(让引用指向该对象)。
如果没有禁止指令重排序,可能发生的顺序为1->3->2,就是在内存上开辟空间后就直接返回地址给引用,这个时候还没真正的初始化完对象。等释放锁后,别的线程进入判断,这时候引用已经不是null了,直接拿去使用,其实这个对象在这个时候可能还是个半成品,那就有空指针异常了。
懒汉式单例有以下特点
- 私有构造。
- 静态私有属性和静态公有方法。
- 线程安全。
- 双重检查(为了兼顾效率和线程安全)。
- volatile禁止指令重排序(防止返回半初始化对象)。
3、静态内部类
1 | public class Cherry { |
静态内部类单例有以下特点
- 私有构造。
- 静态内部类持有静态变量作为单例的实例。
- 静态域由虚拟机初始化一次,保证线程安全。
4、枚举
1 | public enum Durian { |
枚举单例有以下特点
- 防止反射执行私有构造方法,会抛
NoSuchMethodException
异常。 - 防止序列化,反序列化后还是同一个对象。
- 防止克隆,会抛
CloneNotSupportedException
异常。
5、破坏单例的三种方式
- 反射
- 序列化
- 克隆
除了枚举,其它几种单例模式都会被以上3种方式破坏。解决方案如下:
防止反射
定义一个全局变量,当第二次创建的时候抛出异常。1
2
3
4
5
6
7
8private static boolean isCreate = false;
private Apple() {
// 防止反射对单例的破坏
if(isCreate) {
throw new RuntimeException("已然被实例化一次,不能再实例化");
}
isCreate = true;
}防止克隆破坏
重写clone(),直接返回单例对象。1
2
3
4
protected Object clone() {
return apple;
}防止序列化破坏
添加readResolve(),返回单例对象。1
2
3private Object readResolve() {
return apple;
}
参考链接
代码地址
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 赵晓斌技术博客!
评论