单例模式 看这一篇就够了

时间:2022-03-10 18:57:06

一、是什么

       1、单例类只能有一个实例。

       2、单例类必须自己创建自己的唯一实例。

       3、单例类必须给所有其他对象提供这一实例。

 

二、适用环境

       单例模式可以保证全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。故以下情况常考虑单例模式:

       1)系统只需要一个实例对象,或者因为资源消耗太大只允许创建一个实例对象。

       2)客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

 

三、实现方式

3.1  饿汉式单例

当类Singleton1被加载时,类的静态变量instance会被初始化,此时类的构造函数会被调用,单例类的唯一实例将被创建。

优点:无需考虑多线程问题;可以确保实例唯一性。调用速度快,反映时间短。

缺点:资源利用效率低,占用率高。

public class Singleton1 {

    private static Singleton1 instance = new Singleton1();
    
    private Singleton1(){}
    
    public static Singleton1 getInstance(){
        return instance;
    }
}

 

3.2  懒汉式单例

当类Singleton2被加载时,不会创建类的实例,在需要的时候再加载实例,这种技术又叫延迟加载(Lazy Load)。

通过关键字synchronized解决线程安全问题。

优点:资源利用率高。

缺点:线程安全控制繁琐;系统性能会受影响。

public class Singleton2 {

    private static Singleton2 instance = null;  //静态私有成员变量
    
    //私有构造函数
    private Singleton2(){ }
    
    //为避免多个线程同时调用getInstance()方法,使用关键字synchronized对方法加锁,
    //确保任意时刻只有一个线程可执行该方法
    synchronized public static Singleton2 getInstacne(){
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

上述代码虽然解决了线程安全的问题,但是每次调用getInstance()方法时,都要进行线程锁定判断,在多线程高并发环境下会导致系统性能降低,为解决此类问题问题,应将加锁范围缩小到创建实例时。故做如下改进:

public class Singleton2 {

    private static Singleton2 instance = null;  //静态私有成员变量
    
    //私有构造函数
    private Singleton2(){ }
    
    public static Singleton2 getInstance(){
        if (instance == null) {
            synchronized (Singleton2.class) {
                instance = new Singleton2();
            }
        }
        return instance;
    }
}

 

3.3  双重检查锁定

上述3.2懒汉式单例类仍然存在单例对象不唯一的情况,例如某一瞬间线程A和线程B同时调用getInstance()方法,再synchronized加锁机制下,A线程先执行加锁代码块创建实例,线程B处于等待状态。当A执行完毕时,线程B并不知道实例已被创建,将会继续创建一份新的实例,这样就产生了多个实例对象。

注意:使用双重检查锁定实现懒汉式单例类时,需要在静态成员变量instance前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能正确处理,但是需要JDK1.5以上(现在也不用更低的版本了吧...)。

优点:解决懒汉式单例类存在单例不唯一的问题。

缺点:volatile关键字会屏蔽Java虚拟机做的一些代码优化,有时可能会导致系统的运行效率降低。

故将继续改进如下:

public class Singleton3 {

    private volatile static Singleton3 instance = null;
    
    private Singleton3(){ }
    
    public static Singleton3 getInstance(){
        //第一重判断
        if (instance == null) {
            //锁定代码块
            synchronized (Singleton3.class) {
                //第二重判断
                if (instance == null) {
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}

 

3.4  静态内部类实现单例模式  

饿汉式单例类不能实现延迟加载,不管将来用不用单例类,始终都会占据内存;

懒汉式单例类线程安全控制繁琐,且性能受到影响;

在Java中,还可使用静态内部类实现单例模式,既可以实现延迟加载,又可以保证线程安全,不影响系统性能。

public class Singleton4 {

    private Singleton4(){}
    
    private final static class innerClass{
        private static Singleton4 instance = new Singleton4();
    }
    
    public static Singleton4 getInstance(){
        return innerClass.instance;
    }
    
    /*//测试代码段
    public static void main(String[] args){
        Singleton4 s1,s2;
        s1 = Singleton4.getInstance();
        s2 = Singleton4.getInstance();
        System.err.println("s1 = s2? 答案是 " + (s1==s2)); //s1 = s2? 答案是 true
    }*/
}

 

备注:以上代码实现部分源码参照《Java设计模式》-(刘伟)一书。

总结如有错误、不足与改进之处,希望大家指正,不胜感激!

以上!