线程安全的单例的几种实现方法

1. 使用synchronized

饱汉:双重检查锁定饿汉静态内部类枚举 都属于利用synchronized同步原理实现

1.1 饱汉:双重检查锁定(double-checked locking)

public class SingleTon {
  
  // 静态实例变量加上volatile
    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;
    }

1.2 饿汉

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

    private Singleton() {
    }

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

1.3 静态内部类

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {
    }

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

以上的静态内部类、饿汉等模式均是通过定义静态的成员变量,以保证单例对象可以在类初始化的过程中被实例化。

这其实是利用了ClassLoader的线程安全机制。ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。

所以, 除非被重写,这个方法默认在整个装载过程中都是线程安全的。所以在类加载过程中对象的创建也是线程安全的。

1.4 枚举

public enum EnumFactory{ 
    
    singletonFactory;
    
    private MySingleton instance;
    
    private EnumFactory(){//枚举类的构造方法在类加载是被实例化
        instance = new MySingleton();
    }
        
    public MySingleton getInstance(){
        return instance;
    }
    
}
 
class MySingleton{//需要获实现单例的类,比如数据库连接Connection
    public MySingleton(){} 

枚举其实底层是依赖Enum类实现的,这个类的成员变量都是static类型的,并且在静态代码块中实例化的,和饿汉有点像, 所以他天然是线程安全的。

2. 利用CAS原理

public class Singleton {
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();

    private Singleton() {
    }

    public static Singleton getInstance() {
        for (; ; ) {
            Singleton singleton = INSTANCE.get();
            if (null != singleton) {
                return singleton;
            }
            singleton = new Singleton();
            if (INSTANCE.compareAndSet(null, singleton)) {
                return singleton;
            }
        }
    }
}

3. 使用ThreadLocal

与前面的方式不同的是,ThreadLocal 保证的是单个线程内部访问的是同一个实例,不同线程访问的不是同一个实例,即局部单例,并非全局单例。

public class Singleton {
    private static final ThreadLocal<Singleton> singleton = new ThreadLocal<Singleton>() {
        @Override
        protected Singleton initialValue() {
            return new Singleton();
        }
    };
    public static Singleton getInstance() {
        return singleton.get();
    }
    private Singleton() {
    }
}

序列化与反序列化破坏单例问题

序列化对象时,序列化前后得到对象不是同一个实例。解决办法就是在反序列化的过程中使用readResolve()方法,该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉,单例实现的代码如下:

import java.io.ObjectStreamException;
import java.io.Serializable;
 
public class MySingleton implements Serializable {
   
  private static final long serialVersionUID = 1L;
 
  //内部类
  private static class MySingletonHandler{
    private static MySingleton instance = new MySingleton();
  } 
  
  private MySingleton(){}
   
  public static MySingleton getInstance() { 
    return MySingletonHandler.instance;
  }
  
  //该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉
  protected Object readResolve() throws ObjectStreamException {
    System.out.println("调用了readResolve方法!");
    return MySingletonHandler.instance; 
  }

参考链接:

高并发下线程安全的单例模式(最全最经典)

单例模式——线程安全的两种实现

面试官真是搞笑!让实现线程安全的单例,又不让使用synchronized!

版权

评论