单例模式的几种实现方式

1. 静态常量

饿汉式单例,这种情况优点是简单,缺点是加载的太早了,在加载这个类的时候就被实例化出来了

/**
 * 饿汉式单例
 * 缺点:一装载就实例化了,所以不用他,干嘛加载
 * 所以就有了懒汉式加载
 */
public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton getInstance() {
        return instance;
    }
}
2. 静态语句块

使用静态语句块,和1差不多,也是饿汉式的加载,在类被加载时就被实例化了

/**
 * 使用静态语句块
 * 和饿汉式差不多
 */
public class Singleton {
    private static Singleton instance;
    static {
        instance = new Singleton();
    }
    private Singleton(){}

    public static Singleton getInstance() {
        return instance;
    }
}
3. 方法中判断(有问题)

这种方式实现了懒加载,在需要的时候才实例化。缺点是有线程安全问题,在多线程访问时,会被多次实例化

/**
 * 懒加载
 * 懒汉式
 * 缺点:有线程安全问题
 */
public class Singleton {
    private static Singleton INSTANCE;

    private Singleton(){}

    public static Singleton getInstance() {
        if (INSTANCE == null){
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}
4. 方法中判断

这种方式在3上面做了改进,解决了线程安全问题,同时也是懒加载。通过加锁来解决了线程安全的问题,缺点就是锁的粒度太大了,处理在第一次实例化的时候需要加锁保证线程安全,后面就不需要的,因为已经实例化出来了,直接取就行了。

/**
 * 懒加载
 * 懒汉式加载,使用锁来保证线程安全
 * 缺点:锁的粒度太大,instance有值之后可以不需要再加锁了
 */
public class Singleton {
    private static Singleton instance;

    private Singleton(){}

    public synchronized static Singleton getInstance() {
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
5. 减小锁粒度(有问题)

这种方式也是在3上面做了改进,但是看上去可能不会有问题,但是在多线程的情况下,多个线程进入了instance == null这个判断之后,会阻塞在sync之前,当一个线程结束进行实例化之后,后一个线程也会进行实例化,就会产生多个实例出来。

/**
 * 懒加载
 * 懒汉式加载,使用锁来保证线程安全
 * 缺点:有线程安全问题
 */
public class Singleton {
    private static Singleton instance;

    private Singleton(){}

    public static Singleton getInstance() {
        if (instance == null){
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}
6. 双重检查(有问题)

这种情况看上去也是正常的,但是也是有线程安全问题的。问题出在new Singleton();这一步,在这一步会产生指令重排序问题。

/**
 * 懒加载
 * 懒汉式加载,使用锁来保证线程安全
 */
public class Singleton {
    private static Singleton instance;

    private Singleton(){}

    public static Singleton getInstance() {
        //第一次可以保证在初始化完成之后可以不进入锁
        if (instance == null){
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
6.1 new Singleton();的指令重排序

指令重排序是有JVM内部和CPU执行的一个优化代码执行的方式,将没有数据依赖的语句可以执行重排序,可以由CPU的多个核心乱序执行。这样可以保证代码执行的效率,只保证了最终一致性,保证代码最后执行的结果是一致的。

那么在new Singleton();语句中我们查看jvm字节码的话可以看到实际上是有多句的。

0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init>>
7 astore_1
8 return

实际上可能分为了三部分,第一部分是在内存中开辟空间,对象做初始化工作;第二部分是赋初值完成对象初始化工作;第三部分是将内存中的那个空间的地址赋给字段。那么在这三部分中,第二部分和第三部分是没有数据依赖的,是有可能会出现重排序的。那么如果在第三步先执行的情况下,这个时候我们的instance已经有值了,如果第二个线程来获取,就会直接返回,但是对象的初值还没有附上去,就会出现数据错误,所有的值都是初值的情况

7. 双重检查

在这种方式下,使用了volatile关键字来防止指令重排序

/**
 * 懒加载
 * 懒汉式加载,使用锁来保证线程安全
 * 双重检测,可以保证线程安全,也可以保证懒加载
 */
public class Singleton {
    //添加volatile防止指令重排序
    private volatile static Singleton instance;

    private Singleton(){}

    public static Singleton getInstance() {
        if (instance == null){
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
8. 静态内部类

这种方式既保证了线程安全,也保证了懒加载

/**
 * 懒加载
 * 懒汉式加载
 * 静态内部类
 */
public class Singleton {
    private static class SingletonHolder{
        private static Singleton instance = new Singleton();
    }

    private Singleton(){}

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

上方所有方式看上去都没有问题,但是还是有一点小小的问题,就是可以被通过反射修改权限,然后执行构造方法,生成另一个实例。但是一般没人会这么干。

9. 枚举类

使用这种方式可以完美的避开上述的所有问题。这种方式也是在《Effective Java》中推荐的方式。

但是我个人感觉这种方式不太好,就是枚举类型和我们正常使用的类型还是不太一样的。

/**
 * 防止反序列化
 */
public enum Singleton {
    instance;
}

枚举类之所以无法通过反射实例化,是因为在java.lang.reflect.Constructor#newInstance

@CallerSensitive
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    //在这里作出了判断如果为enumerate类型的话,就会抛出异常
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

所以如果有需要使用单例的情况,就选择第一种就行了,如果有懒加载的方式,可以选择第七种或者第八种。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值