Java的单例模式实现方式
几种常见形式
- 饿汉式:直接创建对象,不存在线程安全问题
直接实例化饿汉式(简洁直观)
枚举式(最简洁)
静态代码块饿汉式(适合复杂实例化) - 懒汉式:延迟创建对象
线程不安全的实现(适用于单线程)
线程安全的 (适用于多线程)
静态内部类形式(适用于多线程)
饿汉式
- 性能高、线程安全
- 某些情况下会造成内存浪费
/**
* 饿汗式单例
* 优点:执行效率高、性能高、没有任何锁、线程安全
* 缺点:某些情况下可能会造成内存浪费
*/
public class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return INSTANCE;
}
}
饿汉式(静态块)
/**
* 饿汗式单例-静态块写法
*/
public class HungryStaticSingleton {
private static final HungryStaticSingleton INSTANCE;
static {
INSTANCE = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return INSTANCE;
}
}
懒汉式(线程不安全)
- 线程不安全
/**
* 懒汉式单例模式
* 优点:单例对象在被使用的时候才会初始化
* 缺点:在多线程环境下,就会出现线程安全问题
*/
public class LazySimpleSingleton {
private static LazySimpleSingleton INSTANCE;
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
if (INSTANCE == null){
INSTANCE = new LazySimpleSingleton();
}
return INSTANCE;
}
}
懒汉式(线程安全)
/**
* 懒汉式单例模式
* 优点:单例对象在被使用的时候才会初始化
* 缺点:synchronized 性能低,锁占资源
*/
public class LazySimpleSingleton {
private static LazySimpleSingleton INSTANCE;
private LazySimpleSingleton(){}
public static synchronized LazySimpleSingleton getInstance(){
if (INSTANCE == null){
INSTANCE = new LazySimpleSingleton();
}
return INSTANCE;
}
}
双重锁校验
/**
* 优点:性能高了,线程安全了
* 缺点:可读性难度加大,不够优雅
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//检查是否要阻塞
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
//检查是否要重新创建实例
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
//指令重排序的问题volatile
}
}
}
return instance;
}
}
静态内部类
/**
* 静态内部类单例写法
* 优点: 这种方式兼顾饿汉式单例模式的内存浪费问题和synchronized的性能问题
* 存在问题:无法避免序列化破坏单例
*/
public class LazyInnerClassSingleton {
// 使用LazyInnerClassSingleton的时候,默认先会初始化内部类
// 如果没有使用,则内部类是不加载的
private LazyInnerClassSingleton(){
if(LazyHolder.LAZY != null){ //避免反射机制破坏单例
throw new RuntimeException("不允许创建多个实例");
}
}
// 每一个关键字都不是多余的,static是为了使用单例的共享空间,保证这个方法不会被重写、重载
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
// 默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
枚举单例
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){return INSTANCE;}
}
容器单例
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className){
Object instance = null;
if(!ioc.containsKey(className)){
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
}catch (Exception e){
e.printStackTrace();
}
return instance;
}else{
return ioc.get(className);
}
}
}
举出至少4种单列可能被破坏的场景
饿汗式单例的存在线程安全问题
解决方式:双重校验锁单例
在双重校验锁单例中存在指令重排序问题
通过 添加 volatile关键字解决
private volatile static LazyDoubleCheckSingleton instance;
volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存。这样任何时刻,不同的线程总能看到该变量的最新值。
反射破坏单例
内部静态类方式的单例可以被反射破坏,如下面的代码
try {
Class<?> clazz = LazyStaticInnerClassSingleton.class;
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Object instance1 = c.newInstance();
Object instance2 = c.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1 == instance2);
}catch (Exception e){
e.printStackTrace();
}
解决办法,在构造方法中加入以下代码:
private LazyStaticInnerClassSingleton(){
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允许非法访问");
}
}
在《Effective Java》中推荐使用枚举式单例,因为不会反射破解。
反序列化导致破坏单例
如果单例类实现了Serializable序列化接口,
序列化:把内存中对象的状态转换为字节码的形式,把字节码通过IO输出流,写到磁盘上,永久保存下来,持久化
反序列化:将持久化的字节码内容,通过IO输入流读到内存中来,转化成一个Java对象 。 如下面代码:
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
}
破坏代码
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
解决办法
在序列化类中加入readResolve方法
private Object readResolve(){ return INSTANCE;}