定义
保证一个类只有一个实例,并且提供一个全局访问点
懒汉式
1)线程安全
2)加锁优化=> double check
3)编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化的实例。
- 对象创建的步骤: 分配空间 => 初始化 => 引用赋值 在编译时 第2、3步是可以互换的
- 当步骤互换,先进行引用赋值,当另一个需要获取时,由于还未进行初始化,所以会产生
空指针异常
。 - 所以需要 通过添加
volatile
关键字进行修饰,防止指令重排。
优化过程
- 基本
public class LazySingleton {
private static LazySingleton instance;
//创建私有构造函数,防止使用 new 创建
private LazySingleton(){}
public static LazySingleton getInstance(){
if(instance == null){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
}
}
- 问题
当两个线程几乎同时进入 if ,就会创建两个不同的对象。
解决:添加程序🔒
public synchronized static LazySingleton getInstance(){ if(instance == null){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } instance = new LazySingleton(); } return instance; }
问题 : 当创建实例对象后,不断获取会降低获取效率(不断加锁)。
- 解决 : 只需要在 if 内部添加程序🔒,以提高程序的效率
public static LazySingleton getInstance(){
if(instance == null){
synchronized (LazySingleton.class){
if (instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
最终代码
class LazySingletion{
private volatile static LazySingletion instance;
private static LazySingletion(){
}
public static LazySingletion getInstance(){
if(instance == null){
synchronized (LazySingletion.class){
if (instance == null) {
instance = new LazySingletion();
}
}
}
return instance;
}
}
饿汉式
JVM 保证线程安全的,程序在编译时就创建了对象,所以不存在线程安全问题。
class HangrySingleton{
private static HangrySingleton instance = new HangrySingleton();
private HangrySingleton(){}
public static HangrySingleton getInstance(){
return instance;
}
}
静态内部类
懒加载的方式: 将单例对象 放置在内部类中,当调用 getInstance 方法时,需要内部类的属性,这时会创建 实例对象,是懒加载的方式。
class InnerClassSingleton{
private static class InnerClass{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
// 防止使用反射机制进行创建
// 懒汉模式不能进行防护
if(Inner.instance != null){
throw new RuntimeException("已经创建过实例!~");
}
}
public static InnerClassSingleton getInstance(){
return InnerClass.instance;
}
}
Enum 单例模式
反射安全、线程安全
反射攻击
使用反射机制,可以调用私有构造函数,可能造成打破单例的规则。
private InnerClassSingleton(){
// 防止使用反射机制进行创建
// 懒汉模式不能进行防护
if(Inner.instance != null){
throw new RuntimeException("已经创建过实例!~");
}
}
序列化的单例模式
将对象存入硬盘,读取的对象和调用方法获取的对象不一致。
- 解决:
- 对象中继承序列化接口
Serializable
- 添加方法 :
Object readResolve() throws ObjectStreamException;
- 添加版本🆔 :
static final long serialVersionUID = 43L;
public class InnerSingleton implements Serializable {
static final long serialVersionUID = 43L;
private static class Inner{
private static InnerSingleton instance = new InnerSingleton();
}
private InnerSingleton(){
if(Inner.instance != null){
throw new RuntimeException("已经创建过实例!~");
}
}
public static InnerSingleton getInstance(){
return Inner.instance;
}
Object readResolve() throws ObjectStreamException {
return Inner.instance;
}
}
- 测试
@Test
public void save() throws IOException {
InnerSingleton instance = InnerSingleton.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("InnerSingleton"));
out.writeObject(instance);
out.close();
}
@Test
public void read() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("InnerSingleton"));
InnerSingleton innerSingleton = (InnerSingleton) in.readObject();
in.close();
Assert.assertEquals(true, InnerSingleton.getInstance() == innerSingleton);
//通过
}
Enum类型序列化
Enum 类型可直接进行序列化,不需要进行以上操作。
其他示例
- Spring & JDK
- java.lang.Runtime => 饿汉式
- java.util.Currency => double check
- org.springframework.aop.framework.ProxyFactoryBean
- org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
- Tomcat
- org.apache.catalina.webresources.TomcatURLStreamHandlerFactory