Java学习笔记--单例

一、什么是单例模式?

单例模式(Singleton Pattern),顾名思义,就是被单例的对象只能有一个实例存在。单例模式的实现方式是,一个类能返回对象的一个引用(永远是同一个)和一个获得该唯一实例的方法(必须是静态方法)。通过单例模式,我们可以保证系统中只有一个实例,从而在某些特定的场合下达到节约或者控制系统资源的目的。

二、代码如何实现?

  1. 饿汉模式
    最常见、最简单的单例模式写法之一,即在类的一开始就给它新建一个实例。示例如下:
//饿汉模式
class Singleton {
    
    //一开始就创建一个实例
    private static Singleton instance = new Singleton();
    
    private Singleton() { }
    
    //获取实例
    public static Singleton getInstance() {
        return instance;
    }
}

存在的问题:这种方式有一个明显的缺点,那就是不管有没有调用过获得实例的方法,每次都会新建一个实例。

  1. 懒汉模式
    饿汉模式的升级版本,即在类的一开始只创建一个引用,但并不实例化,只有在需要使用到它的时候,先去判断实例是否为空,如果为空的时候才会新建一个实例来使用。示例如下:
//懒汉模式
class Singleton {

    //一开始并不创建实例
    private static Singleton instance;

    private Singleton() { }

    //需要时再新建
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

存在的问题:该种模式存在一个严重的问题。那就是如果有多个线程并行调用getInstance()的时候,还是会创建多个实例的,单例模式就失效了。

  1. 线程安全的懒汉模式
    在懒汉模式的基础上,把它设为线程同步(synchronized)就好了。synchronize的作用就是保证在同一时刻最多只有一个线程运行,这样就避免了多线程带来的问题。示例代码如下:
//懒汉模式(线程安全)
class Singleton {
    
    private static Singleton instance;

    private Singleton() { }

    //添加了 synchronized 关键字
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

存在的问题:这种模式看似很好的解决了多线程的问题,但是它的效率并不高,每次调用获得实例的方法时都要进行同步,但是多数情况下并不需要同步操作。

  1. 双重检验锁
    为了解决第三种模式的问题,延伸出来双重检验锁模式,即在获取实例的方法中,先去判断该实例是否为空,如果为空在再加同步锁,然后新建实例。示例如下:
//双重检验锁
class Singleton {

    private static Singleton instance;

    private Singleton() { }
    
    public static Singleton getInstance() {
        //第一个检验锁,如果不为空直接返回实例对象,为空才进入下一步
        if (instance == null) {
            //第二个检验锁,因为可能有多个线程进入到if语句内,所以加线程同步锁
            synchronized (Singleton.class) { 
                instance = new Singleton();   
            }
        }
        return instance;
    }
}

存在的问题:该种模式此时并不是完美的。主要问题在在于instance = new Singleton();这句代码,因为JVM(Java虚拟机)执行这句代码的时候,要做好几件事情,而JVM为了优化代码,有可能造成做这几件事情的执行顺序是不固定的,从而造成错误。

解决办法:为了解决上述问题,我们需要给实例加一个volatile关键字,它的作用就是防止编译器自行优化代码。此时,双重检验锁模式才完美实现。

private volatile static Singleton instance;
  1. 静态内部类
    这种方式,利用了 JVM 自身的机制来保证线程安全,因为 SingletonHolder 类是私有的,除了 getInstance() 之外没有其它方式可以访问实例对象,而且只有在调用 getInstance() 时才会去真正创建实例对象。示例如下:
//静态内部类
class Singleton {

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

    public static Singleton getInstance() {
        return SingletonHolder.singleton;
    }
}
  1. 枚举
    我们可以通过 Wife.INSTANCE 来访问实例对象,而且创建枚举默认就是线程安全的,还可以防止反序列化带来的问题。最为推荐。示例如下:
//利用枚举的方式创建单例
public enum Singleton {
    INSTANCE;

    public void whateverMethod() {
        
    }
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容