Java单例模式(Singleton)-创建型

意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

实现(饿汉、懒汉、双重校验锁、静态内部类)

饿汉单例模式

public class Singleton {
	 private static final Singleton instance=new Singleton();//加载类时,自动初始化实例
	 private Singleton(){}//私有构造器
	 public static Singleton getInstance(){//静态工厂方法
	    instance;
	 }
         //其他方法   
}

在这个类被加载时,静态变量instance会被初始化,单例类的唯一实例就被创建出来。

这种方式基于类加载的机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定是否有其他的方式导致类装载(例如调用其他的静态方法),如果不是在调用getInstance()方法的时候初始化instance,显然没有达到lazy loading的效果。

懒汉单例模式(非线程安全)

public class Singleton {
	 private static Singleton instance;
	 private Singleton(){}//私有构造器
	 public static Singleton getInstance(){
	        if(instance==null){
	            instance=new Singleton();
	        }
	        return instance;
	 }   
}

这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。


例如,如果A、B两个线程各自创建了一个实例,显然这违背了单例实例的初衷。

懒汉单例模式(线程安全)

public class Singleton {
	 private static Singleton instance;
	 private Singleton(){}//私有构造器
	 public synchronized static Singleton getInstance(){//在方法上进行同步
	        if(instance==null){
	            instance=new Singleton();
	        }
	        return instance;
	 }   
}

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,因为只有第一次创建实例时需要同步,其余时候不需要同步,但是我们却在每次获取单例时都进行了同步。

静态内部类单例模式

public class Singleton{
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton(){}//私有构造器
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;  
    }
    //其他方法
}

这种方式同样利用了类加载的机制来保证线程安全,它跟饿汉单例模式不同点在于(很细微的差别):饿汉单例模式只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方(其他静态方法)被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比饿汉单例模式就显得很合理。

双重校验锁(非线程安全)

public class Singleton{
    private static Singleton instance;
    private Singleton(){}//私有构造器
    public  static Singleton getInstance(){
        if(singleton==null){//第一次校验
            synchronized(Singleton.class){//同步
                if(singleton==null)//第二次校验
                    singleton=new Singleton();
            }
        }
        return singleton;
    }
}

“双重校验锁”将竞争锁的时机限定在第一次创建实例时,提高了并发性能。但是却存在潜在的线程安全问题。

由于new关键字代表的实例化对象的创建操作,实际上并非是一个原子性操作,而是分为:分配内存空间、初始化对象、引用赋值三个操作。伪代码:

memory = allocate();   // 1:分配对象的内存空间

ctorInstance(memory);  // 2:初始化对象

instance = memory;    // 3:设置instance指向刚分配的内存地址

因为虚拟机的指令重排序,可能出现如下执行过程。



线程B访问了未进行初始化的实例,可能会出现非预期的结果。

双重校验锁(线程安全)

我们可以利用volatile关键字禁止指令重排序。

public class Singleton {
	 private static volatile Singleton singleton;
	 private Singleton(){
	 }
	 public  static Singleton getInstance(){
	        if(singleton==null){
	        	synchronized(Singleton.class){
	        		if(singleton==null){
	        			singleton=new Singleton();
	        		}
	        	}
	        }
	        return singleton;
	 }   
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值