单例模式总结

单例模式总结

总结分享了一下单例模式,方便自己记忆,有不正确的地方欢迎指正。主要从三个方面进行分享:

1.什么是单例模式

    确保某一个类只有一个实例,并且需要自行实例化然后向系统提供这个实例

    共同构成
• 私有的构造方法
• 私有的静态变量(与该类的实例相同)
• 共有的静态方法返回实例

2.为什么会有单例模式

某些对象创建频繁,对于大型系统是很大一笔开销。

只允许一个对象存在,相同的对象多于一个会引起系统混乱。

去掉new,降低内存使用频率,减轻GC压力。

3.单例模式的实现方式

(1)饿汉模式

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton(){}
    public static Singleton getSingleton(){
        return singleton;
    }
}

当类加载的时候就初始化这个实例,创建单例实例简单容易,但是有一个弊端,这个实例有可能没有被使用。

(2)懒汉模式

public class Singleton {
    private static Singleton singleton = null;
    private Singleton(){}
    public static Singleton getSingleton(){
         if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

多线程模式下是不安全的!当两个线程同时进入上述标红的代码时,由于判断singleton时得到的结果都是null,所以两个线程都会创建新的对象,导致线程不安全。

(3)懒汉模式改进第一版(加锁实现线程安全)

public class Singleton {
    private static Singleton singleton = null;
    private Singleton(){}
    public static synchronized Singleton getSingleton(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

直接在方法上加同步锁,缺点:每次获取单例都要同步,而同步的成本较高,所以要缩小synchronized使用的范围

(4)懒汉模式改进第二版(双重检测机制)

public class Singleton {
    private static Singleton singleton = null;
    private Singleton(){}
    public static Singleton getSingleton(){
        if(singleton == null){
            synchronized(Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
    return singleton;
    }
}

获取单例对象时,先进行singleton判空,只有在singleton为空时才进入同步锁,大大提高了性能。

但是上述实现的单例模式并不能保证线程安全,这里涉及到了JVM编译器的指令重排

假设这样的场景,当两个线程一先一后访问getInstance方法的时候,当A线程正在构建对象,B线程刚刚进入方法:


A线程执行的语句instance = new Singleton,会被编译器编译成如下JVM指令:
memory =allocate();    //1:分配对象的内存空间 
ctorInstance(memory);  //2:初始化对象 
instance =memory;     //3:设置instance指向刚分配的内存地址 

但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:
memory =allocate();    //1:分配对象的内存空间 
instance =memory;     //3:设置instance指向刚分配的内存地址 
ctorInstance(memory);  //2:初始化对象 

当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程B抢占到CPU资源,执行  if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。上述原因主要是因为instance = new Singleton并非是原子操作(int i = 9;// 直接赋值操作)。改进方法:在instance对象前面增加一个修饰符volatile

volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。
volatile包含以下语义:
Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。

public class Singleton {
    private Singleton() {}  //私有构造函数
    private volatile static Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
         if (instance == null) {      //双重检测机制
         synchronized (Singleton.class){  //同步锁
           if (instance == null) {     //双重检测机制
             instance = new Singleton();
                }
             }
          }
          return instance;
      }
}
经过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:
memory =allocate();    //1:分配对象的内存空间 
ctorInstance(memory);  //2:初始化对象 
instance =memory;     //3:设置instance指向刚分配的内存地址 
如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。

(5)静态内部类

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton singleton = new Singleton();
    }
    private Singleton(){}
    public static Singleton getSingleton(){
        return SingletonHolder.singleton;
    }
}
静态内部类第一次加载的时候并不会初始化实例,只有在getSingleton()的时候,才会去创建实例。因此这种方式不仅可以保证线程的安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐的使用方式。
下面是测试代码:
//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton implements Serializable{
	
	private static final long serialVersionUID = 1L;

	static {
		System.out.println("外部类Singleton加载...");
	}
	      
	private Singleton() {}
	private static Singleton single = null;
	// 静态工厂方法
	public static Singleton getInstance() {
		if (single == null) {
			single = new Singleton();
		}
		return single;
	}
	// 静态内部类的单例模式
	 private static class SingletonHolder {
		 static {  
	            System.out.println("内部类SingletonHolder加载...");  
	        }
	      private static final Singleton singleton = new Singleton();
	    }
	 
    public static Singleton getSingleton(){
        return SingletonHolder.singleton;
    }

    public static void main(String[] args) {  
    	 Singleton outer = new Singleton();      //此刻其内部类是否也会被加载?  
         System.out.println("===========分割线===========");  
         Singleton singleton = Singleton.getSingleton();     //调用内部类的静态方法  
    } 
}
输出结果如图:


但是问题来了,静态内部类实现的单例模式也存在着单例模式共同的问题:无法防止利用反射机制来重复构建对象.

@Test
public void test() throws Exception {

    Constructor<Singleton> con;
    try {
        con = Singleton.class.getDeclaredConstructor();
        // 设置为可访问
        con.setAccessible(true);
			
        Singleton singleton1 = (Singleton)con.newInstance();
        Singleton singleton2 = (Singleton)con.newInstance();			
        System.out.println(singleton1 == singleton2);
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }	 
}
结果如下:

(6)枚举实现单例模式

public enum SingletonEnum {
    INSTANCE;
}

测试代码如下:

@Test
	public void test1() throws Exception {

	 // 枚举的反射演示
	 Constructor<SingletonEnum> con =
	 SingletonEnum.class.getDeclaredConstructor();
	 con.setAccessible(true);
	 SingletonEnum singleton1 = (SingletonEnum)con.newInstance();
	 SingletonEnum singleton2 = (SingletonEnum)con.newInstance();
	 System.out.println(singleton1.equals(singleton2));
		 
	}

运行时会有异常产生


另外枚举模式实现的单例模式还可以避免序列化与反序列化对象不一致的问题:

@Test
	public void test2() throws Exception {

		// 序列化与反序列化演示
		Singleton singleton = Singleton.getInstance();
		SingletonEnum singleton1 = SingletonEnum.INSTANCE;
		File file = new File("E:\\io\\singleton.txt");
		try {
			file.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			// 单例对象序列化过程
			FileOutputStream fos = new FileOutputStream(file);
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(singleton);
			oos.writeObject(singleton1);
			oos.flush();
			oos.close();
			fos.close();

			// 单例对象反序列化过程
			FileInputStream fis = new FileInputStream(file);
			ObjectInputStream ois = new ObjectInputStream(fis);
			Singleton singleton2 = (Singleton) ois.readObject();
			SingletonEnum singleton3 = (SingletonEnum) ois.readObject();
			ois.close();
			fis.close();

			System.out.println("枚举反序列化对象结果:" + (singleton1 == singleton3));
			System.out.println("普通单例反序列化对象结果:" + (singleton == singleton2));

		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

运行结果如下图:

4.总结:

5.延伸:

Spring框架中的单例模式实现:
public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{  
      
       private final Map singletonCache=new HashMap();  
       public Object getBean(String name)throws BeansException{  
           return getBean(name,null,null);  
       }  
    ...  
       public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{  
         
          String beanName=transformedBeanName(name);  
          Object bean=null;  
         
          Object sharedInstance=null;  
          //使用了代码锁定同步块  
          synchronized(this.singletonCache){  
             sharedInstance=this.singletonCache.get(beanName);  
           }  
          if(sharedInstance!=null){  
             ...  
             //返回合适的缓存Bean实例  
             bean=getObjectForSharedInstance(name,sharedInstance);  
          }else{  
            ...  
            //取得Bean的定义  
            RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);  

            if(mergedBeanDefinition.isSingleton()){  
               synchronized(this.singletonCache){  
                //再次检测单例注册表  
                 sharedInstance=this.singletonCache.get(beanName);  
                 if(sharedInstance==null){  
                    ...  
                   try {  
                      //真正创建Bean实例  
                      sharedInstance=createBean(beanName,mergedBeanDefinition,args);  
                      //向单例注册表注册Bean实例  
                       addSingleton(beanName,sharedInstance);  
                   }catch (Exception ex) {  
                      ...  
                   }finally{  
                      ...  
                  }  
                 }  
               }  
              bean=getObjectForSharedInstance(name,sharedInstance);  
            } 







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值