版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习!

单例,顾名思义一个类只有一个实例。为什么要使用单例模式,或者说什么样的类可以做成单例的?在工作中我发现,使用单例模式的类都有一个共同点,那就是这个类没有状态,也就是说无论你实例化多少个对象,其实都是一样的。又或者是一个类需要频繁实例化然后销毁对象。还有很重要的一点,如果这个类有多个实例的话,会产生程序错误或者不符合业务逻辑。这种情况下,如果我们不把类做成单例,程序中就会存在多个一模一样的实例,这样会造成内存资源的浪费,而且容易产生程序错误。总结一下,判断一个类是否要做成单例,最简单的一点就是,如果这个类有多个实例会产生错误,或者在整个应用程序中,共享一份资源。

在实际开发中,一些资源管理器、数据库连接等常常设计成单例模式,避免实例重复创建。实现单例有几种常用的方式,下面我们来探讨一下他们各自的优劣。

第一种方式:懒汉式单例

 1 public class Singleton {
 2     //一个静态实例
 3     private static Singleton singleton;
 4     //私有构造方法
 5     private Singleton(){
 6
 7     }
 8     //提供一个公共静态方法来获取一个实例
 9     public static Singleton getInstance(){
10
11         if(singleton == null ){
12
13             singleton = new Singleton();
14         }
15
16         return singleton;
17
18     }
19 }

在不考虑并发的情况下,这是标准的单例构造方式,它通过以下几个要点来保证我们获得的实例是单一的。

1、静态实例,静态的属性在内存中是唯一的;

    2、私有的构造方法,这就保证了不能人为的去调用构造方法来生成一个实例;

    3、提供公共的静态方法来返回一个实例, 把这个方法设置为静态的是有原因的,因为这样我们可以通过类名来直接调用此方法(此时我们还没有获得实例,无法通过实例来调用方法),而非静态的方法必须通过实例来调用,因此这里我们要把它声明为静态的方法通过类名来调用;

    4、判断只有持有的静态实例为null时才通过构造方法产生一个实例,否则直接返回。

在多线程环境下,这种方式是不安全,通过自己的测试,多个线程同时访问它可能生成不止一个实例,我们通过程序来验证这个问题:

 1 public class Singleton {
 2     //一个静态实例
 3     private static Singleton singleton;
 4     //私有构造方法
 5     private Singleton(){
 6
 7     }
 8     //提供一个公共静态方法来获取一个实例
 9     public static Singleton getInstance(){
10
11         if(singleton == null ){
12
13             try {
14                 Thread.sleep(5000);  //模拟线程在这里发生阻塞
15             } catch (InterruptedException e) {
16                 e.printStackTrace();
17             }
18
19             singleton = new Singleton();
20         }
21
22         return singleton;
23
24     }
25 }

测试类:

public class TestSingleton {public static void main(String[] args) {Thread t1 = new MyThread();Thread t2 = new MyThread();t1.start();t2.start();}}class MyThread extends Thread{@Overridepublic void run() {System.out.println(Singleton.getInstance()); //打印生成的实例,会输出实例的类名+哈希码值
        }
}

执行该测试类,输出的结果如下:

从以上结果可以看出,输出两个实例并且实例的hashcode值不相同,证明了我们获得了两个不一样的实例。这是什么原因呢?我们生成了两个线程同时访问getInstance()方法,在程序中我让线程睡眠了5秒,是为了模拟线程在此处发生阻塞,当第一个线程t1进入getInstance()方法,判断完singleton为null,接着进入if语句准备创建实例,同时在t1创建实例之前,另一个线程t2也进入getInstance()方法,此时判断singleton也为null,因此线程t2也会进入if语句准备创建实例,这样问题就来了,有两个线程都进入了if语句创建实例,这样就产生了两个实例。

为了避免这个问题,在多线程情况下我们要考虑线程同步问题了,最简单的方式当然是下面这种方式,直接让整个方法同步:

public class Singleton {//一个静态实例private static Singleton singleton;//私有构造方法private Singleton(){}//提供一个公共静态方法来获取一个实例public static synchronized Singleton getInstance(){if(singleton == null ){try {Thread.sleep(5000);  //模拟线程在这里发生阻塞} catch (InterruptedException e) {e.printStackTrace();}singleton = new Singleton();}return singleton;}
}

我们通过给getInstance()方法加synchronized关键字来让整个方法同步,我们同样可以执行上面给出的测试类来进行测试,打印结果如下:

从测试结果可以看出,两次调用getInstance()方法返回的是同一个实例,这就达到了我们单例的目的。这种方式虽然解决了多线程同步问题,但是并不推荐采用这种设计,因为没有必要对整个方法进行同步,这样会大大增加线程等待的时间,降低程序的性能。我们需要对这种设计进行优化,这就是我们下面要讨论的第二种实现方式。

第二种方式:双重校验锁

由于对整个方法加锁的设计效率太低,我们对这种方式进行优化:

 1 public class Singleton {
 2     //一个静态实例
 3     private static Singleton singleton;
 4     //私有构造方法
 5     private Singleton(){
 6
 7     }
 8     //提供一个公共静态方法来获取一个实例
 9     public static Singleton getInstance(){
10
11         if(singleton == null ){
12
13             synchronized(Singleton.class){
14
15                 if(singleton == null){
16
17                     singleton = new Singleton();
18
19                 }
20             }
21         }
22
23         return singleton;
24
25     }
26 }

跟上面那种糟糕的设计相比,这种方式就好太多了。因为这里只有当singleton为null时才进行同步,当实例已经存在时直接返回,这样就节省了无谓的等待时间,提高了效率。注意在同步块中,我们再次判断了singleton是否为空,下面解释下为什么要这么做。假设我们去掉这个判断条件,有这样一种情况,当两个线程同时进入if语句,第一个线程t1获得线程锁执行实例创建语句并返回一个实例,接着第二个线程t2获得线程锁,如果这里没有实例是否为空的判断条件,t2也会执行下面的语句返回另一个实例,这样就产生了多个实例。因此这里必须要判断实例是否为空,如果已经存在就直接返回,不会再去创建实例了。这种方式既保证了线程安全,也改善了程序的执行效率。

第三种方式:静态内部类

 1 public class Singleton {
 2     //静态内部类
 3     private static class SingletonHolder{
 4         private static Singleton singleton = new Singleton();
 5     }
 6     //私有构造方法
 7     private Singleton(){
 8
 9     }
10     //提供一个公共静态方法来获取一个实例
11     public static Singleton getInstance(){
12
13         return SingletonHolder.singleton;
14
15     }
16 }

这种方式利用了JVM的类加载机制,保证了多线程环境下只会生成一个实例。当某个线程访问getInstance()方法时,执行语句访问内部类SingletonHolder的静态属性singleton,这也就是说当前类主动使用了改静态属性,JVM会加载内部类并初始化内部类的静态属性singleton,在这个初始化过程中,其他的线程是无法访问该静态变量的,这是JVM内部帮我们做的同步,我们无须担心多线程问题,并且这个静态属性只会初始化一次,因此singleton是单例的。

第四种方式:饿汉式

 1 public class Singleton {
 2     //一个静态实例
 3     private static Singleton singleton = new Singleton();
 4     //私有构造方法
 5     private Singleton(){
 6
 7     }
 8     //提供一个公共静态方法来获取一个实例
 9     public static Singleton getInstance(){
10
11         return singleton;
12
13     }
14 }

这种方式也是利用了JVM的类加载机制,在单例类被加载时就初始化一个静态实例,因此这种方式也是线程安全的。这种方式存在的问题就是,一旦Singleton类被加载就会产生一个静态实例,而类被加载的原因有很多种,事实上我们可能从始至终都没有使用这个实例,这样会造成内存的浪费。在实际开发中,这个问题影响不大。

以上内容介绍了几种常见的单例模式的实现方式,分析了在多线程情况下的处理方式, 在工作中可根据实际需要选择合适的实现方式。还有一种利用枚举来实现单例的方式,在工作中很少有人这样写过,不做探讨。

转载于:https://www.cnblogs.com/fangfuhai/p/6666850.html

单例模式(含多线程处理)相关推荐

  1. java单例模式及其安全发布(含饿汉式、饱汉式和错误示例)

    适用场景 保证一个类仅有一个实例,并提供一个访问它的全局访问点.例如只应该有一个文件系统来保证文件的正确访问. JDK中 java.lang.Runtime#getRuntime()就是一个典型应用: ...

  2. 设计模式(4)-序列生成器之单例模式

    场景:序列生成器 系统中统一的序列生成程序,整个系统统一一套!那么就用单例模式吧! 首先看看单例模式 1)类持有一个自己的实例,而且还是个静态实例. 2)类的构造函数为私有属性. 3)用以获得实例的方 ...

  3. 阿里天猫、蚂蚁最全一百多道面试题以及阿里P8架构技能树分享(含答案解析)

    [阿里天猫.蚂蚁.钉钉面试题目] 不会做别着急:文末有答案 1. junit用法,before,beforeClass,after, afterClass的执行顺序 2. 分布式锁 3. nginx的 ...

  4. C++实现线程安全的单例模式

    C++实现线程安全的单例模式 1.单例模式 一个简单的单例模式很容易实现:构造函数声明为private或protect防止被外部函数实例化,内部保存一个private static的类指针保存唯一的实 ...

  5. JAVA面试题总汇(含答案)

    1.面向对象的特征有哪些方面    1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节 ...

  6. 【腾讯Bugly干货分享】那些年,我们一起写过的“单例模式”

    题记 度娘上对设计模式(Design pattern)的定义是:"一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结."它由著名的"四人帮",又称 ...

  7. 阅读源码学设计模式-单例模式

    有些编码套路是公认的,大家都参照其编写符合可观赏性的代码,那就是设计模式 现在.NETcore 默认提供了DI功能,那我想设计一个全局的引擎类,进行注入服务.解析服务.配置中间件.并且要求该引擎类全局 ...

  8. 史上最全Java多线程面试60题,含答案大赠送!

    [BAT必考系列!多线程60题] 多线程有什么用? 线程和进程的区别是什么? ava实现线程有哪几种方式? 启动线程方法start()和run()有什么区别? 怎么终止一个线程?如何优雅地终止线程? ...

  9. 史上最全Redis面试49题(含答案):哨兵+复制+事务+集群+持久化等

    最全面试题答案系列 史上最强多线程面试44题和答案:线程锁+线程池+线程同步等 最全MySQL面试60题和答案 史上最全memcached面试26题和答案 史上最全Spring面试71题与答案 今天主 ...

  10. 阿里java架构师面试128题含答案:分布式架构+Dubbo+多线程+Redis

    一.Java基础和高级 1.String类为什么是final的. 2.HashMap的源码,实现原理,底层结构. 3.反射中,Class.forName和classloader的区别 4.sessio ...

最新文章

  1. python3 selenium 无头浏览器 错误 FileNotFoundError: [Errno 2] No such file or directory: 'geckodriver'
  2. 为Linux命令起别名
  3. [解决]电信彩信网关开发错误-SOAP_VERSIONMISMATCH
  4. Fedex Ship Manager Software安装
  5. python中dict转换成list_python里dict变成list实例方法
  6. 深入认识Tigase XMPP Server(上)
  7. Redis4.0.13 安装踩雷记录
  8. ceph怎么搭建文件存储_SUSE专家谈Ceph落地之最佳实践
  9. C#联合Halcon 自定义控件鼠标双击响应事件
  10. Linux 中的vmlinuz
  11. Android个人日记本开发背景,毕业设计(论文)-手机app移动开发论文个人心情日记本的设计实现 .doc...
  12. android 连接魅族手机,android studio连接魅族真机
  13. 2012中国云实践之企业总评榜
  14. 计算机光盘无法格式化,求助,我的电脑无法格式化DVD光盘,提示为“windows无法完成格式化“。求高手。...
  15. 【Unity3D开发小游戏】《太空射击游戏》Unity开发教程
  16. windows 固定桌面图标
  17. CHERRY 键盘 alt 组合键失灵或开始菜单键失灵
  18. matlab1到100求和for_一个简单的MATLAB程序(1到100求和)
  19. 海信75E5K怎么样 海信75E5K和75E5H区别 哪个好
  20. 我的第一个网站——MISDream(四):网站的发布与云服务器的搭建

热门文章

  1. 搭建 zookeeper 和搭建dubbo监控中心
  2. python安装pil库-python第三方库PIL.Image安装
  3. 万物互联的根底就是嵌入式,小到智能手表,大到智能汽车,能落地的支撑之一就是嵌入式。
  4. wav文件头修复_贴唱混音之二——音频修复
  5. 【Python实例第33讲】单变量特征选择
  6. 【5分钟 Paper】Reinforcement Learning with Deep Energy-Based Policies
  7. 工具使用教程(二)【Github上传文件大于100M的文件】
  8. 软件类配置(七)【ubuntu16.04安装netbeans、opencv并配置开发环境。】
  9. Linux汇编调试器EDB
  10. eclipse 添加jar (servlet api)