单例模式

趁有闲暇回顾一下单例模式:

饿汉模式---线程安全

package com.mmall.concurrency.example.singleton;

//饿汉模式
//在类加载时就完成了初始化,所以类加载较慢,但是获取对象的速度快
public class Singleton {
    //用静态变量来存储唯一实例
    private static final Singleton instance = new Singleton();

    //私有化构造函数
    private Singleton() {
        //里面可能有很多操作
        System.out.println("嘿嘿,我创建了呢!");
    }

    //提供一个公共的静态方法,用来返回唯一实例
    public static Singleton getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        getInstance();
    }
}

饿汉模式的优缺点

优点

由于使用了static关键字,保证了在引用这个变量时,关于这个变量的所以写入操作都完成,所以保证了JVM层面的线程安全

缺点

不能实现懒加载,造成空间浪费,如果一个类比较大,我们在初始化的时就加载了这个类,但是我们长时间没有使用这个类,这就导致了内存空间的浪费。

 

懒汉模式---线程不安全

package com.mmall.concurrency.example.singleton;

public class Singleton {
    private static Singleton instance;

    private Singleton() {

    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

可能出现多份实例的情况

 

懒汉模式---线程安全

package com.mmall.concurrency.example.singleton;

public class Singleton {
    private static Singleton instance;

    private Singleton() {

    }

    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 

经过修改后,解决了多份实例的问题,但是因为引入synchronized关键字,对代码加了锁,就引入了新的问题,加锁之后会使得程序变成串行化,只有抢到锁的线程才能去执行这段代码块,这会使得系统的性能大大下降。

 

懒汉模式的优缺点

优点

  • 实现了懒加载,节约了内存空间

缺点

  • 在不加锁的情况下,线程不安全,可能出现多份实例
  • 在加锁的情况下,会是程序串行化,使系统有严重的性能问题

 

 

双重检查锁模式---线程不安全

package com.mmall.concurrency.example.singleton;

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;
    }
}

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

 

要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile关键字,volatile关键字严格遵循happens-before原则,即在读操作前,写操作必须全部完成。添加volatile关键字之后的单例模式代码:

双重检查锁模式---线程安全

package com.mmall.concurrency.example.singleton;

public class Singleton {
    private volatile static Singleton instance;

    private Singleton(){
    }

    public static Singleton getInstance(){
        if(instance==null){
            synchronized (Singleton.class){
                if(instance==null){
                    instance=new Singleton();
                }
            }
        }
        return instance;
    }
}

 

添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

 

静态内部类模式---线程安全

静态内部类单例模式也称单例持有者模式,实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由static修饰,保证只被实例化一次,并且严格保证实例化顺序。静态内部类单例模式代码如下:

package com.mmall.concurrency.example.singleton;

public class Singleton {
    private Singleton() {

    }

    private static class InstanceHolder {
        private final static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return InstanceHolder.instance;
    }
}

 

枚举类实现---线程安全

package com.mmall.concurrency.example.singleton;

public class EnumSingleton {

    //私有化构造函数
    private EnumSingleton() {
    }

    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE;
        private final EnumSingleton singleton;
        //JVM保证这个方法绝对只能调用一次
        Singleton(){
            singleton=new EnumSingleton();
        }
        public EnumSingleton getInstance(){
            return singleton;
        }

    }
}

枚举类实现单例模式是 effective java 作者极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

 

 

 

你可能感兴趣的:(设计模式,单例模式)