前言:为什么会有设计模式?个人觉得就是为了代码看起来更加清晰,让人舒服,最好的描述就是代码简洁、生动、易懂。单例模式是最常见的一种设计模式,雪花算法、spring源码中都有用到,下面我会详细解释单例模式的几种情况,以及涉及到的提问点。
下面我将从两方面讲解:(前提需要了解jvm相关知识点,指令重排、可见性等)
1.单例模式定义及作用
2.单例模式几种基本实现方式
1 单例模式定义及作用
单例模式主要是为了确保实例只有一个,尤其是在多线程环境下。排除反射的条件下,正常的调用静态方法,每次的返回的数据是唯一的。
2 单例模式实现方式
一、懒汉式加载(非并发条件下)
所谓的懒汉加载,其实就是没有new 一个对象,只有在调用的时候才会初始化成功。判断只有持有的静态实例为null时才调用构造方法创造一个实例,否则就直接返回。
public class LazySingLeton {
//定义静态对象
//思考:不是静态对象可以吗?
private static LazySingLeton lazySingLeton;
//私有构造方法
//思考 为什么是私有构造方法??
private LazySingLeton() {
}
//给出一个公共的静态方法返回一个单一实例
//思考: 为什么是静态方法
public static LazySingLeton getInstance(int i){
if (lazySingLeton == null) {
lazySingLeton = new LazySingLeton();
}
return lazySingLeton;
}
}
1.不是静态对象可以吗?
答:不可以,静态实例,带有static关键字的属性在每一个类中都是唯一的。
2.为什么是私有构造方法??
答:限制客户端随意创造实例,即私有化构造方法,此为保证单例的最重要的一步。
3. 为什么是静态方法??
答:给一个公共的获取实例的静态方法,注意,是静态的方法,因为这个方法是在我们未获取到实例的时候就要提供给客户端调用的,所以如果是非静态的话,那就变成一个矛盾体了,因为非静态的方法必须要拥有实例才可以调用。
二、饿汉式加载(非并发) 所谓饿汉加载,就是先new好对象直接赋值,这种的有一个小小的下次瑕疵,就是不用的时候也会有一个内存占用。
`public class HungerSingleton {
//定义静态对象
private static HungerSingleton singLeton = new HungerSingleton();
//私有构造方法
private HungerSingleton() {
}
//给出一个公共的静态方法返回一个单一实例
public static HungerSingleton getInstance() {
return singLeton;
}
}
`
三、synchronized(同步)加载,这个可以用在并发中,但是效率太低了。synchronized 会把其他线程处于挂起状态,造成很多等待。
public class BadSingleton {
private static BadSingleton badSingleton;
private BadSingleton() {
}
private synchronized static BadSingleton getInstance() {
if (badSingleton == null) {
return badSingleton = new BadSingleton();
}
return badSingleton;
}
}
四、双重锁机制实现
这种做法与上面的同步做法相比就要好很多了,因为我们只是在当前实例为null,也就是实例还未创建时才进行同步,否则就直接返回,这样就节省了很多无谓的线程等待时间,值得注意的是在同步块中,我们再次判断了synchronizedSingleton是否为null,解释下为什么要这样做。
假设我们去掉同步块中的是否为null的判断,有这样一种情况,假设A线程和B线程都在同步块外面判断了synchronizedSingleton为null,结果A线程首先获得了线程锁,进入了同步块,然后A线程会创造一个实例,此时synchronizedSingleton已经被赋予了实例,A线程退出同步块,直接返回了第一个创造的实例,此时B线程获得线程锁,也进入同步块,此时A线程其实已经创造好了实例,B线程正常情况应该直接返回的,但是因为同步块里没有判断是否为null,直接就是一条创建实例的语句,所以B线程也会创造一个实例返回,此时就造成创造了多个实例的情况。
public class SynchronizedSingleton {
private static SynchronizedSingleton synchronizedSingleton;
private SynchronizedSingleton() {
}
private static SynchronizedSingleton getInstance() {
if (synchronizedSingleton == null) {
synchronized (SynchronizedSingleton.class) {
if (synchronizedSingleton == null) {
synchronizedSingleton = new SynchronizedSingleton();
}
}
}
return synchronizedSingleton;
}
}
上面这种其实还是会有一些瑕疵,设计到jvm的东西,因为会出现指令重排的情况,如果想禁止这个问题,可以使用以下模式。
五、volatile实现,加入了volatile关键字,就等于禁止了JVM自动的指令重排序优化,并且强行保证线程中对变量所做的任何写入操作对其他线程都是即时可见的。
public class VolatileSingleton {
private volatile static VolatileSingleton volatileSingleton;
private VolatileSingleton() {
}
public static VolatileSingleton getInstance() {
if (volatileSingleton == null) {
volatileSingleton = new VolatileSingleton();
}
return volatileSingleton;
}
}
六、静态内部类实现方式(推荐,雪花算法使用),这种实现方式因为一个类的静态属性只会在第一次加载类时初始化,这是JVM帮我们保证的,所以我们无需担心并发访问的问题。所以在初始化进行一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。另外由于静态变量只初始化一次,所以singleton仍然是单例的。
public class StaticNBSingleton {
private StaticNBSingleton(){}
//静态内部类
private static class NB {
private static StaticNBSingleton staticNBSingleton = new StaticNBSingleton();
}
public static StaticNBSingleton getInstance() {
return NB.staticNBSingleton;
}
}
我等采石之人,当心怀大教堂之愿景!
欢迎关注我的公众号!!搬砖小金