单例模式的写法,和DCL


设计模式是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方式。

单例模式

什么是单例模式

    所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。

单列有如下几个特点:

  • 在Java应用中,单例模式能保证在一个JVM中,该对象只有一个实例存在
  • 构造器必须是私有的,外部类无法通过构造器方法创建该实例
  • 没有set方法,外部类无法调用set方法来创建该实例
  • 提供一个公开的get方法获取唯一的这个实例
    单例模式使用地方
       单列模式一般用在对实例输量有严格要求的地方,比如数据池,线程池,缓存,session会话等等。

饿汉式

class Singleton {
    
    
	
	//1.私化类的构造器
	private Singleton (){
    
    
		
	}
	//2.内部创建类的对象
	//4.要求此对象也必须声明为静态的
	private static Singleton instance = new Singleton ();
	//3.提供公共的静态的方法,返回类的对象
	public static Bank getInstance(){
    
    
		return instance;
	}
}

懒汉式

这里的写法是线程不安全的

class Singleton {
    
    
	
	//1.私化类的构造器
	private Singleton (){
    
    
		
	}
	
	//2.声明当前类对象,没初始化
	//4.此对象也必须声明为static的
	private static Singleton instance = null;
	
	//3.声明public、static的返回当前类对象的方法
	public static Order getInstance(){
    
    
		
		if(instance == null){
    
    
			
			instance = new Singleton ();
			
		}
		return instance;
	}
	
}

线程安全的懒汉式

  • 加synchronized关键字
      此方法是最简单又有效的方法,不过对性能上会有所损失。比如两个线程同时调用这个实例,其中一个线程要等另一个线程调用完才可以继续调用。而线程不安全往往发生在这个实例在第一次调用的时候发生,当实例被调用一次后,线程是安全的,所以加synchronized就显得有些浪费性能。
   public class Singleton {
    
    

     private static Singleton instance ;
     private Singleton(){
    
    }
    public   synchronized  static Singleton getInstance(){
    
    
        if(instance ==null){
    
    
                instance =new Singleton(); 
        }
        return instance ;
    }
}
  • 用双检锁做两次判断,代码如下DCL:
   public class Singleton {
    
    

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

   将synchronized关键字加在内部,也就是说当调用的时候是不需要加锁的,只有当instance为null,并创建对象的时候才需要加锁,性能上有了一定的提升。

但是这样就没有问题了吗?

上面的类的getInstance函数的字节码文件为:
 0 getstatic #2 <泛型/Singleton.instance>
 3 ifnonnull 37 (+34)
 6 ldc #3 <泛型/Singleton>
 8 dup
 9 astore_0
10 monitorenter
11 getstatic #2 <泛型/Singleton.instance>
14 ifnonnull 27 (+13)
17 new #3 <泛型/Singleton>
20 dup
21 invokespecial #4 <泛型/Singleton.<init>>
24 putstatic #2 <泛型/Singleton.instance>
27 aload_0
28 monitorexit
29 goto 37 (+8)
32 astore_1
33 aload_0
34 monitorexit
35 aload_1
36 athrow
37 getstatic #2 <泛型/Singleton.instance>
40 areturn

其中的instance =new Singleton(); 被分解为了

  //首先在堆上面申请一块内存,这个时候对象里面的属性复制为初始值
17 new #3 <泛型/Singleton>
20 dup
//调用构造函数,给成员变了赋值
21 invokespecial #4 <泛型/Singleton.<init>>
//把引用赋值给instance
24 putstatic #2 <泛型/Singleton.instance>

在这里插入图片描述
上图中可以看出线程2 拿到了没有初始化的对象。如果没有看懂,看下图
在这里插入图片描述

   public class Singleton {
    
    

     private static volatile Singleton instance ;
     private Singleton(){
    
    }
    public   static Singleton getInstance(){
    
    
        if(instance ==null){
    
    
            synchronized(Singleton.class){
    
    
            		if(instance==null)
                        instance =new Singleton(); 
                }
        }
        return instance ;
    }
}

  通过加上volatile修饰变量,能够防止指令重排序

volatile的作用

  • 防止指令重排序
  • 保证内存可见

但是这个volatile关键字可能会使得jvm减少对代码的优化,所以运行效率不高

使用静态内部类

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载就不去初始化对象,故而不会占用内存。只有当调用getInstance()方法第一次被调用时,才会去初始化instance,第一次调用getInstance()方法会导致虚拟机加载内部类,这种方法不仅能确保线程安全,也能确保单列的唯一性.

  public class Singleton {
    
    
    private Singleton(){
    
    }
    public static class SingletonPro{
    
    
        private static Singleton  instance=new Singleton();
    }
    public static Singleton getInstance(){
    
    
        return SingletonPro.instance;
    }
}

枚举类

    使用枚举类不仅可以解决线程同步,还可以防止反序列化。

    public enum Singleton{
    
    
    	INSTANCE;
     }

   使用枚举来实现单实例控制会更加简洁,而且JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

总结

  个人觉得最好写的就是静态内部类了,同时效率也高。

猜你喜欢

转载自blog.csdn.net/filling_l/article/details/105929862