单例模式(懒汉式单例 and 饿汉式单例)
1/4 单例模式(单件模式)Singleton Pattern
单例模式,其在整个应用程序的生命周期中只存在一个实例。
本文介绍两种单例模式,以及,多线程并发情况下的懒汉式单例模式改造及代码分析。
2/4 懒汉式单例---需要的时候(首次被调用的时候)才创建实例
public class Singleton { //定义一个私有的静态全局变量来保存该类的唯一实例 private static Singleton singleton; /// <summary> /// 构造函数必须是私有的 /// 这样在外部便无法使用 new 来创建该类的实例 /// </summary> private Singleton() { } /// <summary> /// 定义一个全局访问点 /// 设置为静态方法 /// 则在类的外部便无需实例化就可以调用该方法 /// </summary> /// <returns></returns> public static Singleton GetInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
下面是改进后的懒汉式单例类,使其在多线程的环境下也可以实现单例模式的功能。方案是使用双检锁(双重检查锁定,Double-Check Locking)
lock锁是保证线程同步执行的。当多个线程同时到达,一个线程持有lock锁之后,其他线程处于阻塞状态,直到第一个线程释放lock锁。
第一重 singleton == null 的意义:
这里就涉及一个性能问题了,因为对于单例模式的话,new Singleton()只需要执行一次就 OK 了,
而如果没有第一重 singleton == null 的话,每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的。
为了解决线程同步带来的性能问题,我们加上第一重 singleton == null。
那么,只有在最初singleton ==null 的情况下,才会加锁实现线程同步。注意这里,并发情况下,只要执行到singleton ==null ,那么,当一个线程上锁直到对象初始化释放后,其他blocked的线程也会上锁。也就是说,会存在多个线程持有锁,这时,第二重singleton == null的判断就奏效了。后面的线程虽然持有了锁,但是因为singleton已经不为null,所以不会重复创建。
而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了。
@Slf4j public class SigngletonClass { private static volatile SigngletonClass singleton; // 定义一个只读静态对象,且这个对象是在程序运行时创建的 private static final Object object = new Object(); private SigngletonClass() { } public static SigngletonClass getSingleton() { // 第一重 singleton == null if (null == singleton) { log.info("线程进入第一重 singleton == null"); synchronized (object) { log.info("线程进入同步区"); // 第二重 singleton == null if (null == singleton) { log.info("创建实例start"); try { Thread.sleep(new Random().nextInt(50)); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new SigngletonClass(); log.info("创建实例end"); } } } return singleton; } }
模拟多线程并发访问testcase。用例中有5个线程并发。从后面的执行结果可以看出,一共10个线程,前5个线程都加锁进入了同步区。后面的5个线程在获取单例时,由于singleton 已经不为null,所以程序直接返回了singleton 实例。
import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @Slf4j public class SigngletonClassTest { public static void main(String[] args) throws InterruptedException { ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executorService.execute(() -> { SigngletonClass.getSingleton(); log.info("线程结束"); }); } executorService.shutdown(); executorService.awaitTermination(5, TimeUnit.SECONDS); } }
如下是testcase的执行结果:
18:52:59.778 [pool-1-thread-5] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null 18:52:59.778 [pool-1-thread-3] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null 18:52:59.778 [pool-1-thread-1] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null 18:52:59.793 [pool-1-thread-5] INFO singletonpattern.SigngletonClass - 线程进入同步区 18:52:59.779 [pool-1-thread-2] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null 18:52:59.793 [pool-1-thread-5] INFO singletonpattern.SigngletonClass - 创建实例start 18:52:59.778 [pool-1-thread-4] INFO singletonpattern.SigngletonClass - 线程进入第一重 singleton == null 18:52:59.805 [pool-1-thread-5] INFO singletonpattern.SigngletonClass - 创建实例end 18:52:59.805 [pool-1-thread-5] INFO singletonpattern.SigngletonClassTest - 线程结束 18:52:59.805 [pool-1-thread-4] INFO singletonpattern.SigngletonClass - 线程进入同步区 18:52:59.805 [pool-1-thread-4] INFO singletonpattern.SigngletonClassTest - 线程结束 18:52:59.805 [pool-1-thread-5] INFO singletonpattern.SigngletonClassTest - 线程结束 18:52:59.806 [pool-1-thread-4] INFO singletonpattern.SigngletonClassTest - 线程结束 18:52:59.806 [pool-1-thread-5] INFO singletonpattern.SigngletonClassTest - 线程结束 18:52:59.806 [pool-1-thread-4] INFO singletonpattern.SigngletonClassTest - 线程结束 18:52:59.806 [pool-1-thread-5] INFO singletonpattern.SigngletonClassTest - 线程结束 18:52:59.806 [pool-1-thread-2] INFO singletonpattern.SigngletonClass - 线程进入同步区 18:52:59.806 [pool-1-thread-2] INFO singletonpattern.SigngletonClassTest - 线程结束 18:52:59.806 [pool-1-thread-1] INFO singletonpattern.SigngletonClass - 线程进入同步区 18:52:59.806 [pool-1-thread-1] INFO singletonpattern.SigngletonClassTest - 线程结束 18:52:59.807 [pool-1-thread-3] INFO singletonpattern.SigngletonClass - 线程进入同步区 18:52:59.807 [pool-1-thread-3] INFO singletonpattern.SigngletonClassTest - 线程结束
3/4 饿汉式单例--程序加载的时候就创建实例(jvm类的加载过程)
在c#中,可以使用静态初始化来完成饿汉式单例
public sealed class Singleton { private static readonly Singleton singleton = new Singleton(); private Singleton() { } public static Singleton GetInstance() { return singleton; } }
4/4 结束
- 本文参考 https://www.cnblogs.com/BoyXiao/archive/2010/05/07/1729376.html ,博主在懒汉式改造中,对双检锁的解释比较清晰。不过,博主说的加了双检锁之后,只会存在一个线程进入同步区。我对此存不同观点,抱着认真学习的态度,特意编写上文的testcase并做测试。
- 返回单例的那个方法名,本文是叫getInstance(),有的系统取名是me(),感觉既精简又更易读。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/articles/13731884.html