单例模式(Java)

单例模式

单例模式(Singleton Pattern)是最简单的Java设计模式之一。

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。
来自 百度百科

这种模式涉及到一个单一的类,即该类负责创建自己的对象,同时确保只创建单个对象。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

需要注意的是:

  1. 单例类只能有一个实例。
  2. 单例类的唯一实例必须由自己进行创建。
  3. 单例类必须能够提供这个唯一实例给所有其他对象。

单例模式的构造方式主要有两种:饿汉模式和懒汉模式。
所谓饿汉模式,就是在类装载时构建唯一实例;
而懒汉模式,则是在第一次使用时构建唯一实例。

而单例模式的实现方法有很多种,下面博主将列出6种。




单例模式的实现

饿汉模式

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

没有加锁,执行效率高。但是类加载时就进行了初始化,会浪费内存。


懒汉模式(线程不安全)

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

这种实现方式只在第一次调用时才创建实例对象,对于内存的浪费小,但是因为没有加锁,所以严格意义上它并不算单例模式。


懒汉模式(线性安全)

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

这样的懒汉模式就能够在多线程中很好的工作,但是这种解决方法仍有些弊端,为了实现线程安全,而将整个getInstance方法加了锁,会导致效率低下。


DCL双重监测机制实现单例模式(并非完全是线程安全的)

public class Singleton {
    private Singleton() {
        
    }
    
    private static Singleton instance = null;
    
    public static Singleton getInstance() {
        if (instance == null) {						//1. 第一次检查,不为null表示存在实例,直接返回
            synchronized (Singelton.class) {		//2. 加锁,保证内部代码同步进行
                if (instance == null) {				//3. 第二次检查,不为null表示存在实例
                    instance = new Singleton();		//4. 创建单例对象,也是问题的根源
                }
            }
        }
        return instance;
    }
}

虽然上面的代码看起来似乎很完美,但是这是一个错误的优化,因为涉及到JVM编译器的 指令重排( instance = new Singleton(); 一句涉及指令重排, 本篇博客只讲述单例模式的各种实现方式,这里具体讲解详见博客《小白都能看懂的双重检查锁定与延迟初始化深度解析》 ),因此有一定几率会导致访问到未完成初始化的引用对象。

解决这里指令重排问题的方法一共有两种:

  1. 禁止指令重排(使用volatile关键字)
  2. 允许指令重排,但是不允许其他线程“看到”这个重排序(Holder模式)
使用volatile关键字(仍然使用DCL)

volatile的其中一个特点就是能够禁止指令重排(详见博客《深入理解Java中volatile关键字》),因此保证了指令执行顺序。

public class Singleton {
    private Singleton() {
        
    }
    
    volatile private static Singleton instance = null;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singelton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
静态内部类实现单例模式(Holder模式)
public class Singleton  {
    private static class InstanceHolder {
        private static Singleton instance = new Singleton();
    }
    
    private Singleton() {
        
    }
    
    public static Singleton getInstance() {
        return InstanceHolder.instance;
    }
}

JVM在类的初始化阶段(Class被加载后,且被线程使用之前),会执行初始化。初始化期间,JVM会去获得一个锁,这个锁可以同步多个线程对同一个类的初始化。

Holder模式,就是利用了上面这个特性实现懒加载的,并保证构建单例的线程安全问题。(具体的解决过程也在博客《小白都能看懂的双重检查锁定与延迟初始化深度解析》中有讲解)


枚举实现单例模式

由于目前为止讲到的所有单例模式的实现都无法防止利用反射技术来重复构建对象,此时我们就需要使用枚举来解决这个问题了。

public class EnumSingleton {
	private EnumSingleton() {}
	
	private enum EnumHolder{
		INSTANCE;//public static final EnumHolder实例,在static块初始化
		private EnumSingleton instance=null;//EnumHolder实例的私有变量
		EnumHolder(){
			instance = new EnumSingleton();
		}
	}
	
	public static EnumSingleton getInstance() {
		return EnumHolder.INSTANCE.instance;
	}

}

因为枚举类的构造方法在定义枚举的时候就已经调用了其构造方法创建对象,且枚举的构造方法只执行一次,因此保证了其获取到的对象是同一个对象。

枚举不但能保证线性安全,还能防止反射构建,唯一的缺点就是它不是懒加载的,会浪费内存。

小结

一般情况下,不建议使用第 1 种和第 2 种懒汉模式,建议使用饿汉模式。只有在要明确实现 lazy loading 效果时,才会使用基于静态内部类的实现方式。如果涉及到反序列化创建对象时,可以尝试使用基于枚举的实现方式。如果有其他特殊的需求,可以考虑使用基于volatile的DCL。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单例模式是一种设计模式,它确保一个类只有一个实例,并提供全局访问点。 在Java中,可以通过以下方式实现单例模式: 1. 懒汉单例模式 ```java public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` 这种方式下,只有在第一次使用getInstance()方法时才会创建单例对象。 2. 饿汉单例模式 ```java public class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } } ``` 这种方式下,单例对象在类加载时就已经创建好,因此可以保证线程安全。 3. 双重校验锁单例模式 ```java public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 这种方式下,通过双重校验锁实现了懒加载和线程安全。 4. 静态内部类单例模式 ```java public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } ``` 这种方式下,通过静态内部类实现了懒加载和线程安全。当Singleton类被加载时,静态内部类SingletonHolder不会被加载,只有在第一次调用getInstance()方法时才会加载SingletonHolder类,从而实例化Singleton对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值