创建型模式——单例模式

单例模式简介

单例模式是java创建型模式之一,主要作用是创建唯一对象。


单例模式特点:
1.单例类只有一个实例。
2.单例类必须自己创建自己的唯一实例,即私有化构造方法。
3.单例类必须给其他对象提供这一唯一实例。

单例常见实现

饿汉式

public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {

    }

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

饿汉式单例模式在单例类加载的时候就创建了单例对象,由于类加载有JVM控制执行,其过程是线程安全的,所以饿汉式是线程安全的。
特点:
1.线程安全
2.空间换取时间

懒汉式

public class Singleton {
    private static Singleton instance = null;

    private Singleton() {

    }

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

懒汉式单例模式在单例类加载的时候并没有实例化单例对象,而是在获取单例类唯一对象的方法中,先判断单例对象是否为空,为空的时候实例化单例对象,然后将该对象返回给其他对象。由于第一次获取时单例对象必定为空,所以第一获取时会有实例化对象的过程,执行速度会比之后获取时长。
特点:
1.线程不安全(后面分析)
2.时间换取空间

线程安全下的单例模式

为什么懒汉式不是线程安全的?

如果有多个线程同时第一次调用getInstance方法,在第一个线程判断instance为空进入if语句块准备执行 new Singleton()时,第二个线程也进入了getInstance方法,由于第一个线程还未执行完new Singleton()方法,此时instance对象为空,从而使第二个线程也进入了if语句块。同样的情况可能发生在N个线程中,从而instance可能被初始化N次。这样就失去了单例的唯一性。

synchronized同步getInstance实现线程安全。

public class Singleton {
    private static Singleton instance = null;

    private Singleton() {

    }

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

将getInstance整个方法用sychronized关键字修饰,这样在多线程访问getInstance的时候同时进入方法进行判断的只有一个线程,这样就可以避免多次实例化单例对象。但是这样锁粒度太大了,导致多线程获取实例化对象的效率大大降低。(注释部分同样能保证线程安全,但同样所锁密度太大)

DCL(Double Check Lock)

public class Singleton {
    private static Singleton instance = null;

    private Singleton() {

    }

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

//    public static Singleton getInstance(){
//        if (instance == null)
              //多个线程进入此处,虽然后面的线程会等待锁施放,
              //但是同时也能进入同步代码块,导致实例化多个单例对象,从而线程不安全。
//            synchronized (Singleton.class) {
//                instance = new Singleton();
//            }
//        }
//        return instance;
//    }
}

分析注释部分代码,最后出现了DCL双重锁单例模式。多个线程虽然可以进入第一个instance==null的if语句块,但是由于后续的同步代码块中又判断了一次instance == null,所以并不会实例化多个单例对象。从而保证了线程安全。

DCL的隐患

DCL看似完美,但是依然存在隐患,而这个隐患就在instance = new Singleton()这个句代码上。

在代码看来只有一句,但是jvm在执行这句语句的时候会有3个步骤
1.在java堆上分配一块内存M
2.在M上执行实例化单例对象
3.将M地址指向instance

以上顺序是我们需要的顺序,然而JVM在执行的时候会进行指令重排和优化,优化后的执行顺序会成为:
1.在java堆上分配一块内存M
2.将M地址指向instance
3.在M上实例化单例对象

在多线程中,如果jvm按照优化过后的顺序执行到2的时候,其他线程调用了getInstance()方法,此时在第一个判断instance == null时会返回false,从而绕过同步代码块,直接返回instance对象引用,然而此时instance对象并没有实例化完成,从而在其他线程调用的instance时发生NPE。
解决方法:此处的问题涉及到java并发编程中的有序性,使用volatile关键字修饰instance即可。

java并发编程之原子性,可见性,有序性(还没写^_^)

其他单例模式实现

1.静态内部类实现单例

public class Singleton {

    private Singleton() {

    }

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

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

特点:延迟加载,线程安全

2.枚举实现单例

public enum EnumSingleton {
    /**
     * 单例对象
     */
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

特点:防止反射创建多个单例对象

3.使用容器实现单例

public class SingletonManager {
    private static Map<String, Object> singletonMap = new HashMap<>();

    private SingletonManager() {
    }

    public static void registerService(String key, Object singleton) {
        if (singletonMap.containsKey(key)) {
            throw new RuntimeException("重复注册");
        } else {
            singletonMap.put(key, singleton);
        }
    }

    public static Object getService(String key) {
        if (singletonMap.containsKey(key)) {
            return singletonMap.get(key);
        } else {
            return null;
        }
    }
}

特点:方便管理,android中的服务使用此方式。

Kotlin单例object实现

在使用Kotlin语言时,实现单例是非常简单的

object Singleton{

}

通过kotlinc将Singleton的kt文件编译成class文件:

public final class Singleton{
    public static final SingletonINSTANCE;

    private Singleton() {
    }

    static {
        Singleton var0 = new Singleton();
        INSTANCE = var0;
    }
}

koltin中的object单例使用的是饿汉式实现的,所以是线程安全的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容