单例模式简介
单例模式是一种简单的创建模式,使用单例模式需要注意以下几点:一是该类只能有一个实例;二是该类必须自行创建实例;三是该类必须自行向整个系统提供该实例。单例模式虽然简单,但是有很多细节需要注意,下面我们将会依次进行讲解。
懒汉式(线程不安全)
package com.pattern.demo.Singleton;
/**
* 懒汉式 - 非线程安全
*/
public class Singleton {
private static Singleton instance;
/**
* 将构造方法设为私有的
*/
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
instance = new Singleton();
}
return instance;
}
}
缺点:无法保证线程安全,当两个线程同时执行到 if(null == instance) 语句时,无法保证实例的唯一性。
懒汉式(线程安全)
package com.pattern.demo.Singleton;
/**
* 懒汉式 - 线程安全
*/
public class Singleton {
private static Singleton instance;
/**
* 将构造方法设为私有的
*/
private Singleton() {}
public static synchronized Singleton getInstance() {
if(null == instance) {
instance = new Singleton();
}
return instance;
}
}
缺点:对整个静态工厂方法进行加锁,效率较低,当有多个线程同时获取实例对象时,将会进行线程等待。
饿汉式
package com.pattern.demo.Singleton;
/**
* 饿汉式
*/
public class Singleton {
private static Singleton instance = new Singleton();
/**
* 将构造方法设为私有的
*/
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
缺点:类加载时就会创建实例对象,无法做到懒加载,容易产生垃圾对象,浪费内存。
双检锁式
package com.pattern.demo.Singleton;
/**
* 双检锁式
*/
public class Singleton {
private static Singleton instance;
/**
* 将构造方法设为私有的
*/
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized (Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
优点:保证了线程安全,并且是懒加载的,避免了内存浪费。
缺点:这种方式表面上看没什么问题,但是并非绝对安全,编译器将代码编译成JVM指令时可能会涉及到指令重排的问题。在多线程的环境下,指令重排可能造成不安全的结果。举个例子,代码中 instance = new Singleton(); 语句会被编译器编译成如下JVM指令:
memory=allocate(); //1:分配对象内存空间
ctorInstance(memory); //2:初始化对象
instance=memory; //3:设置instance指向刚才分配的内存地址
这些指令的顺序可能经过JVM和CPU的优化后变为如下顺序:
memory=allocate(); //1:分配对象内存空间
instance=memory; //3:设置instance指向刚才分配的内存地址
ctorInstance(memory); //2:初始化对象
当线程A执行完1、3步骤时,instance对象并没有完成初始化,但是已经不是指向null了。此时如果线程B抢占了CPU资源,执行 if(null == instance) 语句时,返回false,从而返回了一个没有初始化完成的instance对象。
双检锁式(升级版)
要解决上面双检锁式遇到的问题,可以使用volatile修饰符进行修饰。volatile用以声明变量的值可能随时会被别的线程修改,使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效(非volatile变量不具备这样的特性,非volatile变量的值会被缓存,线程A更新了这个值,线程B读取这个变量的值时可能读到的并不是是线程A更新后的值)。volatile会禁止指令重排。
package com.pattern.demo.Singleton;
/**
* 双检锁式 - 使用volatile修饰符
*/
public class Singleton {
private volatile static Singleton instance;
/**
* 将构造方法设为私有的
*/
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized (Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
登记式/静态内部类式
package com.pattern.demo.Singleton;
/**
* 登记式/静态内部类式
*/
public class Singleton {
/**
* 将构造方法设为私有的
*/
private Singleton() {}
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
优点:该种方式利用了ClassLoader的加载机制来实现懒加载,并且保证了线程安全。
缺点:和上面其他方式一样,都无法防止通过反射来重复构建对象。
//通过反射打破单例模式的约束
Constructor constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton1 = (Singleton) constructor.newInstance();
Singleton singleton2 = (Singleton) constructor.newInstance();
枚举
package com.pattern.demo.Singleton;
/**
* 枚举实现单例
*/
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
}
}