好久没写过博客了,最近准备把自己对设计模式的理解写下来,既是作为分享也是为了记录。这一篇我们来一块聊聊单例模式。
单例模式的核心点就是只创建一个实例,我们先看下如何实现的。
package com.guantong.seeing.screening.common; /** * 单例模式 * cxx */ public class Singleton { /** * 私有静态实例 */ private static Singleton instance = null; /** * 私有构造方法,防止被实例化 */ private Singleton() { } /** * 创建唯一实例 * @return */ public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这样就实现了最初版本的单例模式,我们来写个测试类,验证下是否是单例。
package com.guantong.seeing.screening.common; import org.junit.Test; public class SingletonTest { @Test public void testGetInstance() { Singleton result = Singleton.getInstance(); Singleton result1 = Singleton.getInstance(); System.out.println(result); System.out.println(result1); } }
输入结果:
可以看到,内存地址都是一致的,证明是一个单例模式
但是这是单线程的单例模式,如果处于高并发的环境,同时有两个线程F1,F2进入到了getInstance方法,我们来写段代码看看会发生什么
@Test public void testGetInstance1() throws InterruptedException { new Thread(() -> { Singleton instance = Singleton.getInstance(); System.out.println(instance); }).start(); new Thread(() -> { Singleton instance = Singleton.getInstance(); System.out.println(instance); }).start(); }
通过跑多线程环境,我发现这个单例被破坏了。也就是说,当两个线程同时 创建实例的时候,单例就被破坏了,我们可以对这个单例模式加个锁
/** * 创建唯一实例 * @return */ public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
但是这样性能不好,我们可以把锁的范围调小点。
/**
* 创建唯一实例
*
* @return
*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (instance) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
这样解决了我们上面的问题,但是又有一个新问题浮现了。
由于jvm的对象创建机制是 分配地址和初始化是随机顺序的。所以当线程F1被jvm分配内存之后,并且未初始化Singleton之间,F2请求进来了,这时候F2通过代码判断已经有了实例了,便直接拿去用了,
问题就出现了,F2拿去的可能是空的实例,会导致空指针等问题。
这时候我们就需要 volatile锁了
package com.guantong.seeing.screening.common; /** * 单例模式 * cxx */ public class Singleton { /** * 私有静态实例 */ private static volatile Singleton instance = null; /** * 私有构造方法,防止被实例化 */ private Singleton() { } /** * 创建唯一实例 * * @return */ public static Singleton getInstance() { if (instance == null) { synchronized (instance) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
值得注意的是,volatile控制的并不是jvm分配内存地址和初始化的顺序,而是在这两步没完成前不会去调用 if (instance == null) 这一步