单例模式

单例模式

** 作用: 确保一个类只有一个实例,并提供该实例的全局访问点**

1.懒汉模式 – 线程不安全 :

优点: singletonInstance 被延迟实例化,没有用到时不会被实例化,从而节省资源.
缺点:线程不安全,当多个线程进入if(singletonInstance == null) 代码块中,从而多次实例化singletonInstance.

public class Singleton {
    public static Singleton singletonInstance;

    private Singleton(){

    }

    public Singleton getSingletonInstance(){
        if(singletonInstance == null){
            singletonInstance = new Singleton();
        }

        return singletonInstance;
    }

}


2.饥饿模式 – 线程安全

优点: 线程安全 (线程不安全是因为被实例化多次,直接实例化就能避免)
缺点: 失去了延迟实例化的节省资源的好处.

public class Singleton {
    
    public static Singleton singletonInstance = new Singleton();
    
}


3.懒汉式 – 线程安全

缺点:就算被实例化了,但是每次有线程进入方法后,其他线程都需要等待,从而存在性能问题.

public class Singleton {

    public static Singleton singletonInstance ;

    private Singleton(){

    }

    public static synchronized Singleton getSingletonInstance(){
        
        if(singletonInstance == null){
            singletonInstance = new Singleton();
        }
        return singletonInstance;
    }

}
4. 双重校验锁 – 线程安全
public class Singleton {

    public static volatile Singleton singletonInstance ;

    private Singleton(){

    }

    public static  Singleton getSingletonInstance(){
        
        if(singletonInstance == null){
            synchronized (Singleton.class){
                if(singletonInstance == null){
                    singletonInstance = new Singleton();
                }
            }
            
        }
        return singletonInstance;
    }

}
4.1 为什么要双重校验?

考虑以下的实现:

		if(singletonInstance == null){

            synchronized (Singleton.class){
                singletonInstance = new Singleton();
            }

        }

如果只有一重检验,假设有两个线程进入了if(singletonInstance == null) 块,虽然有锁,但是两个线程还是会顺序执行实例化语句,只是执行先后的问题.所以就需要两次校验.

4.2 为什么使用volatile 关键字?

uniqueInstance 采用 volatile 关键字修饰也是很有必要的,**singletonInstance = new Singleton();**这段代码其实是分为三步执行:

1.为 uniqueInstance 分配内存空间
2.初始化 uniqueInstance
3.将 uniqueInstance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getSingletonInstance() 后发现singletonInstance不为空,因此返回 singletonInstance,但此时singletonInstance还未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。


5.静态内部类实现:

当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getSingletonInstance() 方法从而触发 SingletonHolder.singletonInstance;SingletonHolder 才会被加载,此时初始化 singletonInstance 实例,并且 JVM 能确保singletonInstance 只被实例化一次。

这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。

public class Singleton {

    private Singleton(){
    }
  
    private static class SingletonHolder{
        public static Singleton singletonInstance = new Singleton();
    }

    public static  Singleton getSingletonInstance(){
        
        return SingletonHolder.singletonInstance;  
    }

}


6.枚举实现:
public enum Singleton {

    INSTANCE;

    private String objName;


    public String getObjName() {
        return objName;
    }


    public void setObjName(String objName) {
        this.objName = objName;
    }


    public static void main(String[] args) {

        // 单例测试
        Singleton firstSingleton = Singleton.INSTANCE;
        firstSingleton.setObjName("firstName");
        System.out.println(firstSingleton.getObjName());
        Singleton secondSingleton = Singleton.INSTANCE;
        secondSingleton.setObjName("secondName");
        System.out.println(firstSingleton.getObjName());
        System.out.println(secondSingleton.getObjName());

        // 反射获取实例测试
        try {
            Singleton[] enumConstants = Singleton.class.getEnumConstants();
            for (Singleton enumConstant : enumConstants) {
                System.out.println(enumConstant.getObjName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//
// 该实现在多次序列化再进行反序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,
// 并且实现序列化和反序列化的方法。
//
// 该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,
// 然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。
// 该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值