【Java进阶营】设计模式之单件模式

65 篇文章 1 订阅
64 篇文章 1 订阅

有人说单件模式是最简单的模式,因为它只有一个类,但其实它还有一些值得注意的地方,就如:出现并发性时,单件可能已经不是单件了。

先说一下,我们为什么要用到单件模式,当我们用到这些对象如:线程池,缓存,注册表和日志对象等,事实上,这些对象我们只能有一个实例,不然会导致很多问题出现,所以我们要将它弄成单件的。

可能你会说利用程序员之间的约定或是利用全局变量就可以做到啊,如:java的静态变量。是的,但全局变量有一个缺点,就是它必须在程序一开始就要创建好,万一这个对象非常耗费资源,而程序又一直没用到它,那不是很浪费吗?而单件模式就克服了这个缺点。

到底如何实现这个单件模式呢?首先这个类只能实例化一次,那就说明它的构造方法不能公开;那不公开,我又如何实例化它呢?其实我们可以这样子做:(这种是经典的单件模式实现)

public class Singleton {

private static Singleton uniqueInstance;//利用一个静态变量来记录Singleton类的唯一实例

// other useful instance variables here

private Singleton() {}//只有自己才能调用构造器

public static Singleton getInstance() {

            //断点1(为下面文字解析使用)

if (uniqueInstance == null) {

            //断点2

uniqueInstance = new Singleton();//当uniqueInstance不存在时才创建

}

return uniqueInstance;

}

// other useful methods here

}

如果你以为这样已经完美了,那就太天真了。在一个单线程中运行,这样确实已经完美了,但如果有很多个线程并发的情况下,那单件可能就不是单件了。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

如:现在有两个线程A和B,当单件类还没实例化时,A和B线程都执行到断点1,因为单件类还没实例化,所以都可以执行断点2,当A这时先实例化单件类,虽然这时已经实例化单件类了,但这时已经不能阻止线程B再次执行实例化了(因为这时B已经进来了),这时就存在2个实例了,所以这种经典的单件模式实现有点瑕疵。

对于如何处理多线程的问题,我这有三种解决方法。

第一种:

public class Singleton {

private static Singleton uniqueInstance;

// other useful instance variables here

private Singleton() {}

public static synchronized Singleton getInstance() {//比上面的代码中多了一个synchronized

if (uniqueInstance == null) {

uniqueInstance = new Singleton();

}

return uniqueInstance;

}

// other useful methods here

}

没错,就是加线程锁来解决,要求进入getInstance()方法前,要等候别的线程离开该方法,才能执行,就是所不存在两个或以上的线程同时进入这个方法。但其实我们只有第一次执行此方法时才真正需要同步,创建好uniqueInstance变量后,我们就不需要同步这个方法了,而同步会降低性能,是一种累赘。

第二种:

public class Singleton {

    //在静态初始化器中创建单件,保证了线程安全

    private static Singleton uniqueInstance = new Singleton();

private Singleton() {}

public static Singleton getInstance() {

return uniqueInstance;

}

}

这种就是一开始就创建好单件类,而不用延迟实例化的做法,如果创建和运行这个单件类时,负担不太繁重,就可以利用这种方法创建单件,毕竟简单好用嘛。

第三种:(我认为最好的一种,不过需要java 5及以上版本才能使用)

public class Singleton {

private volatile static Singleton uniqueInstance;

private Singleton() {}

public static Singleton getInstance() {

            //断点1

if (uniqueInstance == null) {

              //断点2

synchronized (Singleton.class) {

                    //断点3

if (uniqueInstance == null) {

                          //断点4

uniqueInstance = new Singleton();

}

}

}

return uniqueInstance;

}

}

首先解析volatile这个是有什么用的:用在多线程,同步变量。 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。volatile就是用来避免这种情况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A) ,但不保证原子操作。

还是以两个线程A和B来说明,当单件类还没实例化时,线程A和B同时执行到断点1,判断完之后就会执行到断点2,这时假设A先快一步,执行到断点3并锁住类,线程B只能等待,线程A判断完后,执行断点4并实例化单件类后,离开方法解锁;线程B执行到断点3并锁住类,这时判断实例uniqueInstance已存在,离开并解锁。后面其他一些线程只会执行到断点1,并不会执行到断点2,这样就避免了线程锁所带来的性能问题了。

注意锁前锁后的判断都是必须的,锁前是避免创建实例后其他线程执行线程锁方法,提高了性能;锁后,是避免创建多个实例,不然有多少个线程进入断点2,就会产生多少个实例。

这就是“双重检查加锁”的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值