【设计模式】——单例模式的几种写法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fjj15732621696/article/details/81609909

单例模式:保证一个类仅有一个实例,并提供一个访问他的全局点。

懒汉式,线程不安全

//懒汉式,线程不安全
public class Sington {
    private Sington(){};//让外界不能通过new来创建实例
    private static Sington instance;

    public static Sington getInstance(){  //如果实例存在则直接返回,如果没有存在创建实例返回
        if(instance==null){
            instance=new Sington();
        }
        return instance;
    }

    public static  void main(String[] args){
        Sington s1=Sington.getInstance();
        Sington s2=Sington.getInstance();

        if(s1==s2){
            System.out.println("相同实例");
        }else {
            System.out.println("不是相同实例");
        }

    }
}

上述例子如果实在并发情况下,就不能实现单例的效果。比如线程A在判断instance为空时,进入new操作,但是操作还没有完成,此时线程B刚刚好也运行到判断instance是否为null,那么就可能造成线程B也会就new一个实例。这样就违背了单例原本的含义。

第一步优化:为了解决多线程的同步问题我们可以使用synchronized关键字。

 public static synchronized Sington getInstance(){  //如果实例存在则直接返回,如果没有存在创建实例返回
        if(instance==null){
            instance=new Sington();
        }
        return instance;
    }

使用synchronized关键字可以保证单例,但是这时候就会有性能的问题,因为getInstance整个方法都是线程同步的,这样就限定了访问的速度。这时候我们进行第二次优化

双重检验索  线程安全

public class Singleton {
    private static Singleton instance; //声明静态的单例对象的变量
    private Singleton(){};//私有构造方法

    public static Singleton getInstance(){//外部通过此方法可以获取对象
        if(instance==null){
            synchronized (Singleton.class){//保证了同一时间只能只能有一个对象访问此同步块
                if(instance==null){
                    instance=new Singleton();
                }
            }
        }
        return instance;//返回创建好的对象   
    }
}

双重检验锁模式:是一种使用同步块加锁的方法。因为有两次检查,一次在同步块外,一次在同步块内。为什么同步块内还要检验一次呢?因为可能会有多个线程一起进入同步块外的if,如果在同步块内不进行就可能生成多个实例了。这段代码看起来没有问题,其实不然。主要在于instance=new Singleton(),这并非一个原子操作,实际上在

JVM中大概会做三件事。

1、给instance分配内存

2、调用Singleton的构造函数来初始化成员变量

3、将instance对象指向分配的内存空间。(执行完这步instance就是非null了)

但是JVM在即时编译器中存在指令重排序的优化。也就是说上面的第2、3步顺序是不能保证的,执行的顺序可能是1-2-3或者1-3-2。如果是后者,则在3执行完毕、2未执行之前,被线程二抢占了,这是instance已经是非null了(但是却没有初始化),所以线程二就会直接返回instance,然后使用,就会报错

解决方法:只需要将instance声明称volatile就可以了。使用原因:volatile是禁止指令重排序优化。也就是说在volatile变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会重排序到内存屏障之前

饿汉式 线程安全

public class Singleton{
    //类加载时就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}
 
    public static Singleton getInstance(){
        return instance;
    }
}

这种方法非常简单,因为单例的实例对象被声明成了static和final变量,在第一次加载类到内存时就会进行初始化,所以创建实例本身是线程安全的。但是这个模式也有缺点,就是不管是否调用实例,都已经加载到了内存中。

静态内部类  线程安全

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

这种写法仍然使用JVM本身机制保证了线程安全的问题,由于SingletonHolder是私有的,除了本类中的getInstance方法没有办法访问他,所以他其实是懒汉式的。而在读取的实例的时候不会进行同步,又没有性能缺陷。也不依赖于jdk的版本,所以建议使用。

猜你喜欢

转载自blog.csdn.net/fjj15732621696/article/details/81609909