设计模式——单例模式详解
一、饿汉模式
public class Test {
private static Test test = new Test();
public static Test getObject(){
return test;
}
private Test(){
//私有化构造器避免被实例化
}
}
饿汉式顾名思义就是应用程序很饿了,急需一个对象,所以当类被加载的时候直接就创建一个类的对象,使用时通过getObject()方法获取
饿汉模式是线程安全的
风险:饿汉模式由于类加载时候就创建一个对象,无论是否需要创建都创建出来了,那么如果这个程序不是“饿”的就会浪费空间
而懒汉模式只有用的时候才会创建所以会节约空间,但是每次想创建的时候都会询问是否创建过了,所以会浪费很多时间
饿汉模式已经可以满足绝大多数的应用场景
二、懒汉模式
public class Test {
private static Test test;
public static Test getObject(){
if(test==null){
test = new Test();
}
return test;
}
private Test(){
//私有化构造器避免被实例化
}
}
懒汉模式使用懒加载的思想,当调用getObject方法获取单例时才会实例化对象优化饿汉模式内存浪费的问题
不过因为每次获取都需要判断是否存在所以懒汉模式线程是不安全,所以生产环境我们一般是不用懒汉模式的
三、双重检查锁
public class Test {
private static volatile Test test;
public Test getObject() {
if (test == null) {
synchronized (Test.class) {
if (test == null) {
return new Test();
}
}
}
return test;
}
private Test(){
//私有化构造器避免被实例化
}
}
双重检查锁是基于懒汉模式的升级,既能保证线程安全又能实现懒加载,在创建前先用synchronized 将类对象锁住保证线程安全
如果不用Volatile会怎么样?
对象实际上创建对象要进过如下几个步骤:
- 分配内存空间。
- 调用构造器,初始化实例。
- 返回地址给引用
但是可能发生指令重排序的,那有可能构造函数在对象初始化完成前就赋值完成了,在内存里面开辟了一片存储区域后直接返回内存的引用,这个时候还没真正的初始化完对象。
但是别的线程去判断instance!=null,直接拿去用了,其实这个对象是个半成品,那就有空指针异常了。
存在风险
但是双重检查锁存在风险,因为jvm优化执行速度在保障happen-before的原则下会产生指令重排序的情况,虽然可以通过volatile 关键字禁止指令重排序,但在jdk低版本并不能完全禁止,所以会出问题
四、枚举类
//Test.object获取对象
public enum Test {
object;
Test(){
}
}
枚举其实没什么好说的,枚举是java的一个语法糖,实际上是一个继承了enum的final类。但是枚举是一个天然的单例模式,用法也非常简单,安全性也是最高的,因为其他方法虽然保证了应用程序方面的安全性,但是面对暴力反射构造创建对象还是没有办法的。当然,也可以通过构造器里抛异常来解决暴力反射,具体可以看看StandardCharsets这个类,他就是通过在构造器里抛异常来阻止暴力反射的。
枚举的缺点也很明显的,因为他没法实现懒加载,
五、静态内部类模式
//getObject()获取单例
public class Test {
private static class objectClass{
private static Test object = new Test();
}
public static Test getObject(){
return objectClass.object;
}
private Test(){
//私有化构造器避免被实例化
}
}
静态内部类就是java实现单例的各种方法中相对最好的方法。只要构建一个静态内部类,并在内部类中构造单例对象。这样既保证了多线程情况下的安全性,同时也实现了懒加载。极力推荐大家使用这种方法
六、CAS法
该方法转自笔者一位好友https://ovvow.com/posts/singleton/
public class SingletonTest {
private static AtomicReference<SingletonTest> CAS_INSTANCE = new AtomicReference<>();
public static SingletonTest getCasInstance() {
for (; ; ) {
SingletonTest singleton = CAS_INSTANCE.get();
if (singleton != null) {
return singleton;
}
singleton = new SingletonTest();
if (CAS_INSTANCE.compareAndSet(null, singleton)) {
return singleton;
}
}
}
}
优点:既可以懒加载又线程安全
缺点:虽然能保证能拿到对象是单例的,但并不能保证只实例化一个对象,如果这是一个比较“笨重”的对象,这样就会加重虚拟机内存的负担。
七、总结
绝大多数情况直接用饿汉模式足以,其余情况用静态内部类
更多推荐
所有评论(0)