首页 > 代码库 > 单例设计

单例设计

常见的单例设计模式有以下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("文件删除成功");
    }
} 

解决的办法很简单
就是在这个单例对应的类当中添加一个方法 
技术分享
或者直接使用上面提到的枚举来构造单例

单例设计