单例模式

什么是单例模式

保证一个类只有一个实例,并且提供一个访问该实例的全局入口。

饿汉式

在类加载的时候就立即初始化并且创建单例对象。

public class HungrySingle {

    // final对象创建后就不可变
    private static final HungrySingle single = new HungrySingle();

    // 构造器私有,防止外部创建对象
    private HungrySingle() {
    }

    public static HungrySingle getInstance(){
        return single;
    }
}

优点:没有加任何的锁,效率比较高,在线程出现以前就已经实例化了是线程安全的。

缺点:类加载的时候就初始化,不管你是否使用都创建对象,有可能会存在资源浪费。不支持延迟加载。

懒汉式

在需要使用的时候才进行实例化,延迟加载。

public class LazySingle {

    private static LazySingle single = null;
	// 构造器私有,防止外部调用
    private LazySingle() {
    }

    public static LazySingle getInstance(){
        if (single == null) {
            single = new LazySingle();
        }
        return single;
    }
}

优点:延迟加载。

缺点:在高并发的情况存在线程安全问题,可能有多个线程进入到if条件里面。懒汉模式怎么实现线程安全,可以在getInstance方法上加上synchronized锁。

双重检验模式

懒汉模式的改进,通过双重检验减少了不必要的同步提高了性能。但是在高并发的情况下存在指令重排的问题。

public class LazySingle {
	
    private volatile static LazySingle single = null; // 1、volatile修饰

    private LazySingle() {
    }

    public static LazySingle getInstance(){
        if(single==null){ //2:减少不要同步,优化性能
            synchronized (LazySingle.class){ // 3:同步,线程安全
                if(single==null){
                    single = new LazySingle(); //4:创建singleton 对象
                }
            }
        }
        return single;
    }
}

我们可以看到双重检验模式在懒汉是的基础上,改成了双重判空检验。加入有A、B两个线程,同时通过了第一个判空检验,假设A先获取到锁,那么B线程进行等待,A线程进入锁内部,此时single对象为空,所以创建对象然后释放锁,返回对象。然后B线程获取到锁,进入锁内部,但是由于A线程已经创建了single对象,所以B线程什么都不做,之间返回对象,从而保证线程安全。

为什么我们要在single变量上面加上volatile呢?

因为虽然我们已经使用synchronized进行同步,但在第4步创建对象时,会有下面的伪代码:

memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间

当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以JVM是允许的。如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getsingleton方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!

静态内部类模式

和饿汉式一夜同样利用类加载机制,保证线程安全。很好的将饿汉式和懒汉式结合在一起,既实现延迟加载,又保证了系统性能。

public class StaticSingle {

    private StaticSingle(){}

    private static class StaticSingleHolder{
        private static StaticSingle single = new StaticSingle();
    }

    public static StaticSingle getInstance(){
        return StaticSingleHolder.single;
    }
}

从代码我们可以看到getInstance方法通过调用StaticSingleHolder静态内部类来返回StaticSingle,这里为什么这么写呢?

首先我们需要明白的当StaticSingle类加载的时候,StaticSingleHolder静态内部类是没有被加载的(这里和静态变量在类加载的时候初始化不一样,请注意),静态内部类只有在getInstance方法中被主动使用的时候才会被加载。

枚举模式

枚举模式有三个好处1.实例的创建线程安全,确保单例。2.防止被反射创建多个实例。3.没有序列化的问题。

public enum SingleEnum {  
    //实例化对象  
    INSTANCE;  
      
    /** 
     * 对象需要执行的功能 
     */  
    void getInstance(){  
          
    }  
}  

反射/序列化,获取对象,以及防止方式

public class SingleLazy implements Serializable{  
    //提供静态的全局变量 作为访问该类实例的入口 但是这里不立即加载  
   private static SingleLazy sh = null;  
      
      
    /** 
     * 构造器私有 无法创建对象 
     */  
    private SingleLazy(){  
       if(sh!=null){  
            throw new RuntimeException();  
        }  
        System.out.println("构造函数被调用了");  
    }  
      
    /** 
     * 对外提供get方法获取 该类的实例 
     * @return 
     * @throws InterruptedException  
     */  
    public static synchronized SingleLazy getInstance() {  
        if(sh==null){  
            sh = new SingleLazy();  
        }  
        return sh;  
          
    }  
      
    private Object readResolve()throws ObjectStreamException{  
        return sh;  
    }  
      
}

参库博客:

https://www.jb51.net/article/162539.htm

https://segmentfault.com/a/1190000018000917

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值