如何写出高效的单例模式?

基于七种方式创建单例对象

Posted by MasterJen on November 16, 2018

Hey Singleton

Never put off what you can do today until tomorrow. – 今日事今日毕.

最近在看一本书,叫做java性能优化指南,收获颇丰,推荐大家有时间可以看一看,毕竟性能优化一直是我们的一个劣势.

大家都知道,单例模式是一种非常重要的结构模式,因此如何写出高效的单例模式也是很重要的,现在的面试中,有的面试官也会经常问一些单例问题,所以基于此,下面给出了七种方法实现单例.

  • 懒汉式

第一种 线程不安全

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

第二种 线程安全

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

第三种 双重校验锁 线程安全

双重判断 第一个判断是为了提高性能,第二个判断是第一次实例化需要.
/**
 * 懒汉式 单例
 */
public class SingletonOne {
    private SingletonOne() {
        System.out.println("创建了Singleton");
    }
//    私有属性
    private static SingletonOne singletonOne = null;

    public static SingletonOne getSingletonOne() {
//        提高性能
        if (singletonOne == null) {
            synchronized (SingletonOne.class) {
//                判断是否含有此对象
                if (singletonOne == null) {
                    singletonOne =  new SingletonOne();
                }
            }
        }
//        返回此单例
        return singletonOne;
    }
}

第四种 静态内部类 (线程安全)

通过静态内部类的方式进行创建单例,基于类加载器的实现机制,完成了单例的创建,效率更高,并且不用加锁,当静态类加载的时候,并没有创建该单例,只有调用方法时,才会进行创建单例.

/**
 * 懒汉式 单例模式
 */
public class SingletonTwo {
//    私有构造方法
    private SingletonTwo(){
        System.out.println("创建了单例");
    }
//    静态内部类
    private static class SingletonHolder{
        private static SingletonTwo singletonTwo = new SingletonTwo();
    }
//  得到 单例
    public static SingletonTwo getInstance(){
        return SingletonHolder.singletonTwo;
    }
}

第五种 枚举 线程安全

public enum  SingleTon {
    INSTACNE; //理解为public static final Singleton INSTANCE;
    SingleTon(){
    }
    int getValue(){
        return 1+90;
    }
}

测试:

   public static void main(String[] args) {
          SingleTon instacne = SingleTon.INSTACNE;
          int value = instacne.getValue();
          System.out.println(value);
     }
     
这样很简单,线程时安全的,并且避免了序列化和反射攻击.
  • 饿汉式

第六种 饿汉式 通过静态赋值 得到 单例对象 线程安全

这种方式基于classloader机制,避免了多线程的同步问题

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

第七种 通过静态代码块 对单例对象进行赋值 线程安全

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

但是 上面的通过类加载机制完成的创建单例模式 有着一点小问题

1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例.假定不是远端存取,

例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例.    

所以 针对此问题 可以用下面方法解决 

   // 通过 类的权限定名称 返回其Class类 里面确保了 使用同一个 类加载,用来保证加载时,创建用一单例
   private static Class getClass(String classname)   throws ClassNotFoundException {     
         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
         if(classLoader == null)     
           classLoader = Singleton.class.getClassLoader();      
         return (classLoader.loadClass(classname));     
      }     
  }  

2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原.不管怎样,如果你序列化一个单例类的对象,

接下来复原多个那个对象,那你就会有多个单例类的实例

解决方案:
   
// 通过 他的  readResolve方法 保证实现一个单例
 public class Singleton implements java.io.Serializable {     
    public static Singleton INSTANCE = new Singleton();     
   protected Singleton() {     
    }     
    private Object readResolve() {     
              return INSTANCE;     
    }    
 }  

总结下来 双重判定锁的创建单例以及静态内部类的创建单例,还是效率挺高的,但是为了安全,为了防止反射,推荐大家使用枚举方法。 ​