A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

单例模式有一下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。
  单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。比如打印机打印的时候,同时打印多份文件,但是打印机只有一台。
计算机的每个端口,每次只能绑定一个程序

首先,能够想到的最简单的实现是,
把类的构造函数写成private的,从而保证别的类不能实例化此类,然后在类中提供一个静态的实例并能够返回给使用者。
这样,使用者就可以通过这个引用使用到这个类的实例了。
  1. //饿汉式单例(在类在加载的时候就会实例化)
  2. class Singleton
  3. {
  4.         private static final Singleton instance = new Singleton();
  5.         private Singleton()        {}
  6.         public static Singleton getInstance()
  7.         {
  8.                 return instance;
  9.         }
  10. }
复制代码

但是有一个问题就是这个类的加载的时候就会创建一个对象,我们极端一点,假如这个对象很占内存,并且初始化很耗时间的时候,如涉及IO操作,会很浪费,然后我们更极端一点,如果这个程序涉及到这个单例对象的功能我们一直都没有用到的时候,那就会更加浪费了。于是就有了一种想法,我们可不可以在用到的时候再初始化对象呢,回答是可以的,于是就有了以下的代码,俗称懒汉式,就是懒到别人用的时候才开始实例化
  1. //懒汉式单例(主要是可以延迟加载)
  2. class Singleton
  3. {
  4.         private static Singleton instance = null;
  5.         private Singleton()        {}
  6.         public static Singleton getInstance()
  7.         {
  8.                 if (instance == null)
  9.                 {
  10.                         instance = new Singleton();
  11.                 }
  12.                 return instance;
  13.         }
  14. }
复制代码


直到第一次使用的时候通过判断是否为null来创建对象。第一次的时候发现instance是null,然后就新建一个对象,返回出去;第二次再使用的时候,
因为这个instance是static的,所以已经不是null了,因此不会再创建对象,直接将其返回。代码是很好的,单线程下,这段代码没有什么问题,可是如果是多线程,麻烦就来了。极端一点,我们现在有N条线程在进入这个getInstance(),再极端一点这些线程都是在进入if代码块之后,将要实例化对象的时候,CPU执行别的进程了,然后这些进入了if代码块的线程就全部等在了那里了。等CPU从别的进程切换回来的时候,这些N多的线程就会分别实例化对象,这个时候的单例模式就不是单例模式了。
这时候,该怎么办呢?

解决的方法也很简单,那就是加锁,没错,就是在使用多线程的时候使用到的同步代码块,或者叫加锁
代码如下:
  1. //一层检查的懒汉式
  2. class Singleton
  3. {
  4.         private static Singleton instance = null;
  5.         private Singleton()        {}
  6.         public static Singleton getInstance()
  7.         {
  8.                 //同步锁
  9.                 synchronized(Singleton.class){
  10.                 if (instance == null)
  11.                 {
  12.                         instance = new Singleton();
  13.                 }}
  14.                 return instance;
  15.         }
  16. }
复制代码

这里我们加上了synchronized。可以保证只会有一个单例的对象,但是,这个synchronized是很影响性能的,我们极端一点,假如又有N多条的线程在访问getInstance()方法的话。那我们就更纠结了。能不能不要每次要访问getInstance()的时候都要走同步锁的代码块啊。
答案是可以的,因为有问题出现了,总归是有问题的解决方案的。
解决方案在这里。
  1. class Singleton
  2. {
  3.         private static Singleton instance = null;
  4.         private Singleton()        {}
  5.         public static Singleton getInstance()
  6.         {
  7.                 if(instance==null){
  8.                 //同步锁
  9.                 synchronized(Singleton.class){
  10.                 if (instance == null)
  11.                 {
  12.                         instance = new Singleton();
  13.                 }}}
  14.                 return instance;
  15.         }
  16. }
  17. 以上就是传说中的单例模式懒汉双重检查了,听说面试的时候经常会考的哦。各位没事多琢磨琢磨。
复制代码


我的总结:
饿汉式单例类在自己被加载时就将自己实例化。即便加载器是静态的,在饿汉式单例类被加载时仍会将自己实例化。单从资源利用效率角度来讲,
这个饿汉式比懒汉式单例类稍差些,从速度和反应时间角度来讲,则比懒汉式单例类稍好些。然而,懒汉式单例类在实例化时,
必须处理好在多个线程同时首次引用此类时的访问限制问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,
而资源初始化很有可能耗费时间。这意味着出现多线程同时首次引用此类的机率变得较大。



你们以为这就完了吗,你们错了,我在看单例模式的时候还看到了这些东西,由于太过高深,又不是自己社视频总结出来的,但是又想给大家看看别人的牛逼程度,还是偷了一些别人的东西过来,还有读书人不管这个叫偷,嘿嘿,以下代码是别人的哦,

单例模式懒汉双重检查方法在单核和 多核的cpu下都不能保证很好的工作。导致这个方法失败的原因是当前java平台的内存模型。
java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。正是这个机制导致了双重检查加锁方法的失效。
这个问题的关键出现在对象实例化的时候instance = new Singleton();
这行其实做了两个事情:
一、调用构造方法,创建了一个实例。
二、把这个实例赋值给instance这个实例变量。
可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,instance已经被设置为非空了。下面我们看一下出问题的过程:
  1、线程A进入getInstance()方法。
  2、因为此时instance为空,所以线程A进入synchronized块。
  3、线程A执行 instance = new SingletonThree(); 把实例变量instance设置成了非空。(注意,实在调用构造方法之前。)
  4、线程A退出,线程B进入。
  5、线程B检查instance是否为空,此时不为空(第三步的时候被线程A设置成了非空)。线程B返回instance的引用。(问题出现了,这时instance的引用并不是Singleton的实例,因为没有调用构造方法。)
  6、线程B退出,线程A进入。
  7、线程A继续调用构造方法,完成instance的初始化,再返回。

  好吧,继续努力,解决由“无序写”带来的问题。这里用到了一种叫内部类的方法,很牛逼
代码也贴出来给大家看看吧
  1. //使用了内部类的单例模式,线程安全
  2. class SingletonClass
  3. {
  4.         private static class SingletonClassInstance
  5.         {
  6.                 private static final SingletonClass instance = new SingletonClass();
  7.         }
  8.         public static SingletonClass getInstance()
  9.         {
  10.                 return SingletonClassInstance.instance;
  11.         }
  12.         private SingletonClass()
  13.         {
  14.         }
  15. }
复制代码

还有别人的解释
在这一版本的单例模式实现代码中,我们使用了Java的静态内部类。这一技术是被JVM明确说明了的,因此不存在任何二义性。
在这段代码中,因为SingletonClass没有static的属性,因此并不会被初始化。
直到调用getInstance()的时候,会首先加载SingletonClassInstance类,这个类有一个static的SingletonClass实例,
因此需要调用SingletonClass的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。
由于这个instance是static的,因此并不会构造多次。由于SingletonClassInstance是私有静态内部类,所以不会被其他类知道,
同样,static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。
同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。
 最后,再总结一下:
  1、如果单例对象不大,允许非懒加载,可以使用饿汉式。
  2、如果需要懒加载,且允许一部分性能损耗,可以使用方法单层检查的懒汉式
  3、如果需要懒加载,且不怕麻烦,可以使用方法双层检查的懒汉式。

  4、如果需要懒加载,没有且!推荐使用方法四。




好累了,看视频看了大半天,查资料也看了半个晚上,代码也写了半个晚上,写这个帖子又用了小半个晚上,不容易啊,代码全部手打,直接复制可用哦,名位求助攻啊。



2 个回复

倒序浏览
写的真心好,顶一个!
回复 使用道具 举报
不错,很全面,将Java单例模式的主要问题都写出来了
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马