单例设计
常见的单例设计模式有以下7种
1.懒汉 线程不安全
public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
所谓懒汉 , 就是初始化类的时候不创建实例 , 什么时候使用再创建
类似于一种懒加载的机制
但是由于没有线程安全的保障 , 不同的线程很可能会获得不同的实例
所以并不能算是真正严格的单例模式
2.懒汉 线程安全
与第一种形式类似 , 只需要在getInstance方法上面加上 synchronized修饰即可
保证了不同的线程只能拿到唯一的实例 , 实现了线程安全和懒加载
但是效率比较低 , 因为在很多的情况下是不需要同步的
3.饿汉
public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
这种方式基于类加载器的机制 , 关于类加载器在后面的笔记里面再做介绍
有效避免了多线程的同步问题 , 但是并没有绝对实现懒加载
( 在这个类被装载的时候才会创建该单例对象
但是导致类被装载的原因可以有多种 , 比如类中的其他静态方法被调用
并不一定是调用了getInstance方法 )
4.饿汉 ( 变种 )
与第三种形式类似 , 只是把对象的创建放在static静态子句中进行
但是实际效果并没有本质的区别
同样是在类被装载的时候创建该单例对象
5.静态内部类
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton(){} public static final Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
将这个实例包装在一个静态内部类当中 , 实际的效果就是
当Singleton类被装载的时候 , 其中的静态内部类并不会被立即装载
而只有在调用getInstance方法的需要使用这个静态内部类的属性的时候 , SingletonHolder才会被装载
这就彻底解决了饿汉模式所没有彻底解决的懒加载问题
6.枚举
枚举本身就是单例的一种扩展
它是线程安全的 , 而且也可以防止反序列化的时候重新创建单例对象 ( 反序列化问题后面会提到 )
可以说是一种最完美的方式
但是枚举这种十分特殊的类平时很少用到 , 实际使用起来可能略有些生疏
public enum Singleton { INSTANCE; //这里也可以写一些这个类的其他方法.. }
7.双重校验锁
public class Singleton { private volatile static Singleton singleton; private Singleton() {} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
使用双重校验锁可以解决线程安全的懒汉模式所带来的性能问题
因为当实例已经创建的时候 , 就不需要进入synchronized代码块
序列化与反序列化对单例的破坏
如果一个类实现了Serializable接口 , 那么它的对象就可以被序列化写入到一个文件当中
同样也可以读取这个文件重新获取这个类的对象
但是如果作为一个单例 , 当执行反序列化的时候 , 我们希望拿到的还是这个唯一的对象
但是实际情况是会重新创建出一个对象
public static void main(String[] args) throws IOException, ClassNotFoundException{ FileOutputStream fos = new FileOutputStream("tempFile"); ObjectOutputStream output = new ObjectOutputStream(fos); //将对象序列化写出到文件 output.writeObject(Singleton.getInstance()); output.close(); //从文件中读取内容反序列化为对象 FileInputStream fis = new FileInputStream("tempFile"); ObjectInputStream input = new ObjectInputStream(fis); Singleton newSingleton = (Singleton) input.readObject(); System.out.println(newSingleton == Singleton.getInstance()); /*比较获得的结果是false,代表这是两个不同的对象*/ input.close(); //删除临时文件 File file = new File("tempFile"); if(file.delete()){ System.out.println("文件删除成功"); } }
解决的办法很简单
就是在这个单例对应的类当中添加一个方法
或者直接使用上面提到的枚举来构造单例