单例模式简介
单例模式是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单例使用的是饿汉式实现的,所以是线程安全的。