单例模式(懒汉式单例 and 饿汉式单例)
原创
©著作权归作者所有:来自51CTO博客作者buguge的原创作品,请联系作者获取转载授权,否则将追究法律责任
本文介绍两种单例模式,以及,多线程并发情况下的懒汉式单例模式改造及代码分析。
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 结束
- 本文参考 ,博主在懒汉式改造中,对双检锁的解释比较清晰。不过,博主说的加了双检锁之后,只会存在一个线程进入同步区。我对此存不同观点,抱着认真学习的态度,特意编写上文的testcase并做测试。
- 返回单例的那个方法名,本文是叫getInstance(),有的系统取名是me(),感觉既精简又更易读。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge