线程安全的单例模式

单例模式是一种常用的设计模式,其定义是单例对象的类只能允许一个实例存在。下面来看看几种常见的单例模式的写法,以及如何保证线程安全的实现。

1、饿汉式(线程安全)

  这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。但是在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

public class Singleton {

    private static final Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return instance ;
    }
}

2、懒汉式(线程安全)

  这种方式效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。也就是我们之前提到的同步的粒度太粗,synchronized 同步代码应该是越细越好。

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

3、懒汉式(线程安全)的细粒度优化(双重锁机制)

  对上一种模式进行优化,这里判断了两次是否为 null 是因为在并发环境中当线程一执行了第一个判断的时候是为null,可此刻另外一个线程正好执行完初始化操作,在释放锁以后该线程并不知道已经初始化,如果此刻进入代码块不进行再次判断会再初始化一次,这就违背了单例模式的初衷了。

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;
     }
 }

4、静态内部类(懒加载,线程安全)

  这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有懒加载的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。避免了线程不安全,延迟加载,效率高。

public class Singleton {

    private Singleton() {}
    //内部类在外部类调用的时候才会被初始化
    // 内部类一定要在方法调用之前初始化
    private static class SingletonInstance {
        private static final Singleton instance = new Singleton();
    }
    // static 使单例空间共享
    // final使得方法不能被重写重载
    public static final Singleton getInstance() {
        return SingletonInstance.instance;
    }
}

  这里可以在私有的构造方法中进行一个双重锁的判断,定义一个 flag来判断该构造是否被重复调用,来防止反射的侵入。

  除此之外还可以使用枚举类的方式来实现单例模式。由于实际工作中并未发现有人这么做,这里就不演示了。

原文地址:https://www.cnblogs.com/wuzhenzhao/p/9923309.html