Java单例模式(Singleton)-创建型 - Go语言中文社区

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;
	 }   
}
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/sunxianghuang/article/details/51815524
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-02 04:43:17
  • 阅读 ( 891 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢