JAVA设计模式之单例模式

一、单例模式简介

在应用系统开发中,我们常常有以下需求:

  • 在多个线程之间,比如servlet环境,共享同一个资源或者操作同一个对象
  • 在整个程序空间使用全局变量,共享资源
  • 大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。
    因为Singleton模式可以保证为一个类只生成唯一的实例对象,所以这些情况,Singleton模式就派上用场了。

二、单例模式的结构

在这里插入图片描述

三、单例模式的具体实现

假设有一个巧克力工厂,但是工厂只有一个巧克力熔化炉,所以,我们在创建工厂的时候,只能有一个实例,此时我们需要用到单利设计模式。

1、饱汉式

所谓饱汉式,就是在需要的时候才进行创建实例对象
首先我们进行ChocolateFactoryC类设计,这是经典的单例设计模式:

// An highlighted block
package design.single.gys.classic;
public class ChocolateFactoryC {
 private boolean full;
 private boolean boiled;
 private static ChocolateFactoryC singlefactory;
 private ChocolateFactoryC() {};
 public static ChocolateFactoryC getInstance() {
  if(singlefactory==null) {
   for(int i=0;i<100000;i++)
   singlefactory=new ChocolateFactoryC() ;
   }  
  return singlefactory;
 }
 public void fill()
 {
  if(!full) 
   System.out.println("加入巧克力");
  else
   System.out.println("已加满巧克力");
  full=true;
 }
 public void boil()
 {
  if(!boiled) 
   System.out.println("加热巧克力");
  else
   System.out.println("已加热巧克力");
  boiled=true;
 }
 public void finish()
 {
  System.out.println("倒出巧克力");
  full=false;
  boiled=false;
 }
}

在单线程的程序运行过程中,似乎没有什么问题.测试程序如下:

// An highlighted block
package design.single.gys.classic;
public class Test1 {
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  ChocolateFactoryC cf1=ChocolateFactoryC.getInstance();
  ChocolateFactoryC cf2=ChocolateFactoryC.getInstance();
  cf1.fill();
  cf2.fill();
  cf1.boil();
  cf2.boil();
  System.out.println(cf1==cf2);
  System.out.println(cf1);
  System.out.println(cf2);
 }
}

测试结果1:

// An highlighted block
加入巧克力
已加满巧克力
加热巧克力
已加热巧克力
true
design.single.gys.classic.ChocolateFactoryC@10dea4e
design.single.gys.classic.ChocolateFactoryC@10dea4e

可以看到,cf1和cf2是同一个引用,地址相同,且在运行巧克力工厂的时候不会重复加入巧克力重复加热,巧克力工厂就能很好的运行了,喜欢巧克力的人们也就很开心了。
然而,问题来了。如果是多线程条件下,巧克力工厂还能很好的运行吗?下面我们用一个多线程的测试程序来测试这个工厂:

// An highlighted block
package design.single.gys.classic;
import java.util.HashSet;
public class Test implements Runnable{
 private static HashSet<ChocolateFactoryC> hs=new HashSet<ChocolateFactoryC>();
 @Override
 public void run() {
  // TODO Auto-generated method stub
  ChocolateFactoryC cf=ChocolateFactoryC.getInstance();
  synchronized(Test.class) {
  hs.add(cf);
 }
}
 public static  void test()
 {
  for(int i=0;i<1000;i++)
   new Thread(new Test()).start();
 }
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  test();
  synchronized(Test.class) {
  System.out.println("集合大小:"+Test.hs.size());
  for(ChocolateFactoryC c: hs)
   System.out.println(c);
  }
 }
}

在这个程序中,我们创建了1000个线程,每个线程都想去创建这个工厂,但是工厂只有一个啊,我们用一个HashSet 来保存所有线程得到的工厂,最后输出集中保存了多少个不一样的工厂。
测试结果2:

// An highlighted block
集合大小:3
design.single.gys.classic.ChocolateFactoryC@19758f
design.single.gys.classic.ChocolateFactoryC@16d60d5
design.single.gys.classic.ChocolateFactoryC@38e772

集中保存了三个不一样的工厂,当然每次运行的结果都会不一样。这下糟了,明明只有一个工厂,这是却多出了两个不应该存在工厂,程序变得混乱的,工厂没法正常工作,巧克力爱好者们不开心了,大闹了起来。
看来,经典单例设计模式不太符合我们的要求。为了满足巧克力爱好者们,我们不得不进行改进。

2、饿汉式

由于在饱汉式中我们在需要工厂时才进行实例化,但是在多线程中下面这行代码会出现明显的问题,当一个线程执行到if语句中的循环时(为了效果明显,特地增加了循环),该线程随时可能被剥夺执行权,此时的判断语句输出位true,转而下一个线程进行执行,同样判断语句输出也是true,这里就是问题所在,此时将创建两个工厂实体对象。

// An highlighted block
if(singlefactory==null) {
    //此处之后线程随时可能会被剥夺执行权
   for(int i=0;i<100000;i++)
   //此处之前线程随时可能会被剥夺执行权
   singlefactory=new ChocolateFactoryC() ;
   }  

我们对其进行改进,将饱汉式改为饿汉式,我们直接创建工厂实例,在需要的时候,我们调用静态的getInstance()方法获得工厂对象。
代码如下:

// An highlighted block
package design.single.gys.improve;
public class ChocolateFactoryD {
 private static ChocolateFactoryD singlefactory=new ChocolateFactoryD();
 private ChocolateFactoryD() {};
 public static ChocolateFactoryD getInstance() {
 return singlefactory;
 }
}

测试代码:

// An highlighted block
package design.single.gys.improve;
import java.util.LinkedHashSet;
public class Test implements Runnable{
 private static LinkedHashSet<ChocolateFactoryD> hs=new LinkedHashSet<ChocolateFactoryD>();
 @Override
 public void  run() {
  // TODO Auto-generated method stub
  ChocolateFactoryD cf=ChocolateFactoryD.getInstance();
  synchronized(Test.class) {
  hs.add(cf);
  }
 }
 public static  void test()
 {
  for(int i=0;i<1000;i++)
   new Thread(new Test()).start();
 }
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  test();
  System.out.println("集合大小:"+hs.size());
   for (ChocolateFactoryD c: hs)
    System.out.println(c);
 }
}

测试结果:

// An highlighted block
集合大小:1
design.single.gys.improve.ChocolateFactoryD@19758f

此时返回的对象必定是同一个工厂了,那么巧克力爱好者么又开始欢呼了。
有一天巧克力工厂宣布要关闭,为什么呢?工厂24小时在运作,成本太高,已经远远超出巧克力爱好者们的消费需求。也就是说,我们创建的的工厂,即使我们不需要,也会一直存在内存中,占用内存。还没高兴多久的人们又开始躁动了。
没办法,我们继续改进。

3、改进的单例模式

在饱汉式中,因为getInstance()方法运行到一半时,可能会被剥夺执行权,那么我们使用同步方法进行改进。

// An highlighted block
package design.single.gys.synch;
public class ChocolateFactory1 {
 private static ChocolateFactory1 singlefactory=null;
 private ChocolateFactory1() {};
 public static synchronized ChocolateFactory1 getInstance() {
  if(singlefactory==null)
   {singlefactory=new ChocolateFactory1();}
  return singlefactory;
 }
}

使用上面相同的测试代码,测试结果如下:

// An highlighted block
集合大小:1
design.single.gys.synch.ChocolateFactory2@38e772

如我们所愿,只出现了一个工厂。是否这就满足了我们的要求呢?为什么工厂的运作速度这么慢?因为对整个方法使用synchronized同步,对系统的开销太大。
好,继续改进:

// An highlighted block
package design.single.gys.synch;
public class ChocolateFactory2 {
 private static ChocolateFactory2 singlefactory=null;
 private ChocolateFactory2() {};
 public static  ChocolateFactory2 getInstance() {
  if(singlefactory==null) {
   synchronized(ChocolateFactory2.class) {
    if(singlefactory==null)
     {singlefactory=new ChocolateFactory2();}
    }
  }
 return singlefactory;
 }
}

在这里的代码中,我们不对整个方法进行同步,我们对方法内进行双重检查加锁机制,仍然使用if进行判断,接着使用synchronized同步,进行原子操作,里面再次进行if判断,是否已经存在工厂的实例。

// An highlighted block
集合大小:1
design.single.gys.synch.ChocolateFactory2@19758f

emmmm,现在我们的巧克力工厂能很好的运行了,巧克力爱好者们欢呼雀跃。

要抱抱才会开心啊~~~~~~~~~~~~

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值