欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

设计模式之美—单例模式

程序员文章站 2023-11-18 19:59:10
单例模式 什么是单例? 应用场景 代码实现 饿汉式 中国古代神话中有女娲补天一说,现在天破了,我们去求女娲补天。 女娲用英语来说是 A Goddess In Chinese Mythology,意思就是神话中的女神,女娲是独一无二的,现在我们就建一个女神类Goddess。 神话中,我们都是女娲造出来 ......

单例模式

什么是单例?

  1. 该类只能有一个实例。
  2. 该类负责创建自己的对象。
  3. 在整个项目中都能访问到这个实例。

应用场景

  1. 读配置文件时,维护全局的config类。
  2. 线程池、连接池,统一进行分配和管理。
  3. 网站的计数器,也可以采用单例模式实现,保持同步

代码实现

饿汉式

   中国古代神话中有女娲补天一说,现在天破了,我们去求女娲补天。

  女娲用英语来说是 a goddess in chinese mythology,意思就是神话中的女神,女娲是独一无二的,现在我们就建一个女神类goddess。

1 public class goddess {
2     
3 }

  神话中,我们都是女娲造出来的,人是不能造女娲的,所以要女娲私有化构造。

1 public class goddess {
2     private goddess(){};//私有化构造
3 }

  既然人不能女娲,那女娲是怎么来的,女娲伴随天地初开产生的,所以要自己创建对象

1 public class goddess {
2     private static final goddess goddess = new goddess();//自己创建对象
3     private goddess(){};//私有化构造
4 }

  女娲是神秘的,凡胎肉眼看不到所以要private,static保证了女娲伴随天地初开,在内存中永生,不能被垃圾回收器回收。final保证女娲是不会变的,永远是那个女神,啧...啧...

1 public class goddess {
2     private static final goddess goddess = new goddess();//自己创建对象
3     private goddess(){};//私有化构造
4     public static goddess getinstance(){//获取唯一可用的对象
5         return goddess;
6     }
7 }

  既然单例不能被实例化,就需要一个静态的方法来获取对象。这是单例的“饿汉式”,代码第二行就产生了女娲。

  我们来验证一下:

1 public class goddesstest {
2     public static void main(string[] args){
3         goddess goddes1 = goddess.getinstance();
4         goddess goddes2 = goddess.getinstance();
5         system.out.println("两个对象的引用是否相等:"+(goddes1==goddes2));
6     }
7 }

  结果:

两个对象的引用是否相等:true

  两个对象的引用是同一个对象,说明我们实现了单例模式。

懒汉式

 1 public class goddess {
 2     private static goddess goddess ;//此时不创建对象
 3     private goddess(){};//私有化构造
 4     public static goddess getinstance(){//获取唯一可用的对象
 5 
 6         if (goddess == null) {//如果没有女娲才去创建
 7             goddess = new goddess();
 8         }
 9         return goddess;
10     }
11 }

  女娲比较懒,没事干的时候,处于混沌状态,第一次求女娲时,女娲才会实例化。

  好处:省了一段时间的内存,

  坏处:第一次请女娲会慢,因为要消耗cpu去实例化。多线程下也有问题,如果多个人同时请女娲,会产生很多女娲,不是线程安全。

  我们加上synchronized进行优化,线程调用前必须获取同步锁,调用完后会释放锁给其他线程用,也就是请女娲必须排队,大家一个一个来。

 1 public class goddess {
 2     private static goddess goddess ;//此时不创建对象
 3     private goddess(){};//私有化构造
 4     public static synchronized goddess getinstance(){//获取唯一可用的对象
 5 
 6         if (goddess == null) {//如果没有女娲才去创建
 7             goddess = new goddess();
 8         }
 9         return goddess;
10     }
11 }

  然而,这样多个人都要去抢锁,即使对象已经实例化,没有充分利用cpu资源并发优势(特别是多核情况)。

  我们把synchronized放到方法体内,如果女娲还没实例化,才回去抢锁,这就极大的利用了cpu资源。

  代码如下:

双检锁/双重校验锁

 1 //这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
 2 public class goddess {
 3     private volatile static goddess goddess ;//此时不创建对象
 4     private goddess(){};//私有化构造
 5     public static goddess getinstance(){//获取唯一可用的对象
 6         if (goddess == null) {//如果女娲还没有实例化,请女娲的人进行等待,准备抢夺请求锁。
 7             synchronized(goddess.class){
 8                 if (goddess == null) {//第一个抢过之后,后面的人也不用等待了,女娲还有五秒到达战场
 9                     goddess = new goddess();
10                 }
11             }
12         }
13         return goddess;
14     }
15 }

 枚举模式

1 public enum goddess {
2     instance;
3     public goddess getinstance(){
4         return instance;
5     }
6 }

  以上方式是在不考虑反射和序列化的情况下实现的,如果考虑了反射,就无法做到单例类只能有一个实例这种说法了。但是枚举单例模式可以避免这两种情况。

我们以饿汉式为例:

 1 public class goddesstest {
 2     public static void main(string[] args) throws exception {
 3         goddess goddes1=goddess.getinstance();
 4         goddess goddes2=goddess.getinstance();
 5         constructor<goddess> constructor=goddess.class.getdeclaredconstructor();
 6         constructor.setaccessible(true);
 7         goddess goddes3=constructor.newinstance();
 8         system.out.println("正常情况下,两个对象的引用是否相等:"+(goddes1 == goddes2));
 9         system.out.println("反射攻击时,两个对象的引用是否相等:"+(goddes1 == goddes3));
10     }
11 }

  结果:

1 正常情况下,两个对象的引用是否相等:true
2 反射攻击时,两个对象的引用是否相等:false

  枚举单例下示例:

 1 public class goddesstest {
 2     public static void main(string[] args) throws exception {
 3         goddess goddes1=goddess.instance;
 4         goddess goddes2=goddess.instance;
 5         constructor<goddess> constructor= null;
 6         constructor = goddess.class.getdeclaredconstructor();
 7         constructor.setaccessible(true);
 8         goddess goddes3= null;
 9         goddes3 = constructor.newinstance();
10         system.out.println("正常情况下,两个对象的引用是否相等:"+(goddes1 == goddes2));
11         system.out.println("反射攻击时,两个对象的引用是否相等:"+(goddes1 == goddes3));
12     }
13 }

  结果:

1 exception in thread "main" java.lang.nosuchmethodexception: xxx.xxx.goddess.<init>()
2     at java.lang.class.getconstructor0(class.java:3082)
3     at java.lang.class.getdeclaredconstructor(class.java:2178)
4     at com.slw.design.danli.goddesstest.main(goddesstest.java:11)

  反射通过newinstance创建对象时,会检查该类是否enum修饰,如果是则抛出异常,反射失败。所以枚举是不怕发射攻击的。