几种单例设计模式介绍、如何破坏单例模式、如何完善写一个安全的单例。

单例设计模式是一种比较简单的设计模式,涉及到的内容主要是static、synchronized、volatile关键字、内部类、对象克隆、序列化、枚举类型、反射和类加载机制等。

从表面上看,Singleton希望并限制该类的实例只能有一个,主要是由该类的构造方法的通常是private构造方法、static该实例以及返回该实例的getInstance方法。

下面将从多种单例的实现进行解说:

1、饿汉式单例

 1 public class EagerSingleton{
 2     private static final EagerSingleton INSTANCE = new EagerSingleton();
 3
 4     private EagerSingleton(){
 5
 6     }
 7
 8     public static EagerSingleton getInstance(){
 9         return INSTANCE;
10     }
11 }

这种设计模式称谓“饿汉式”,顾名思义,该实例在类加载的时候就会自动创建,不管是不是被使用。所以如果该类的实例化的开销比较大,则该方式并不是最有的实现方法。优点是:无需担心锁线程同步获取该实例时可能出现的并发问题。

优点:1、线程安全2、在类加载的同时已经创建好了一个静态对象,调用时反应速度快。

缺点:1、资源效率不高,可能getInstance()永远不会执行到。

2、懒汉式单例

 1 public class LazySingleton{
 2     private volatile static LazySingleton INSTANCE = null;
 3
 4     private LazySingleton(){
 5     }
 6
 7     public static synchronized LazySingleton getInstance(){
 8         if(INSTANCE == null){
 9             INSTANCE = new LazySingleton();
10         }11         return INSTANCE;12     }
13 }

这种方式也叫做“懒汉式”单例,只有在使用时次进行实例化,避免多线程在并发场景下可能导致的多创建出一个实例的弊端,getInstance方法必须要加上synchronized方法或者采用synchroized代码块来加锁实现,但是这种过度保护的代价非常昂贵,只要是在该实例未被创建时才有必要进行加锁控制并发,因此更多的时候是没有必要同步的,此方法并发经济划算。

3、Lazy Signleton with Double Check

 1 public class LazySingletibWithDoubleCheck{
 2      private volatile static LazySingletonWithDoubleCheck INSTANCE = null;
 3
 4      private LazySingletonWithDoubleCheck(){
 5
 6      }
 7
 8      public static LazySingletibWithDoubleCheck getInstance(){
 9          if(INSTANCE == null){
10              synchronized (LazySingletibWithDoubleCheck.class){
11                  if(INSTANCE == null){
12                      INSTANCE = new LazySingletibWithDoubleCheck();
13                 }
14              }
15          }
16          return INSTANCE;
17      }
18 }

是LazySingleton的改良版本,采用double-check的实现方式避免了对getInstance方法加锁。在实例尚未被实例化时,存在两次检查的流程,第一次检查如果实例已经存在就可以直接返回,反之则进行第二次检查,原因在于可能出现多个线程同时通过了第一次检查,此时必须通过加锁机制实现真正实例化时的排他性,保证只有一个线程成功抢占到锁并执行。目前该方法已经被广泛弃掉,替代的是Lazy initialization holder。

4、Inner Class Singleton

 1 public class InnerClassSingleton{
 2     private static class SingletonHolder{
 3         private static final InnerClassSingleton INSTANCE = new InnerClassSingleton{
 4         }
 5     }
 6
 7     private InnerClassSingleton(){
 8     }
 9
10     public static final InnerClassSingleton getInstance(){
11         return SingletonHolder.INSTANCE;
12     }
13 }

静态内部类的静态常量实例化,INSTANCE是常量,因此只能赋值一次,而且它是静态的,因此随着内部类一块加载。该写法使用了JVM本身机制保证了线程的安全;由于InnerClassSIngleton是私有的,除了getInstance()之外没有办法访问到它,因此它是懒汉式;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖JDK的版本。

以上几种方法是常见的Singleton的方式。如何保证全局的唯一性,如何通过一些手段方法去破坏这唯一性,单纯通过绕开传统Singleton实现中仅靠将构造函数私有化达成的单例从而创建出多个实例。

1、Break Signleton with Clonable

 1 public class CloneableSingleton implements Cloneable{
 2     private static final CloneableSingleton INSTANCE = new CloneableSingleton();
 3
 4     private CloneableSingleton(){
 5     }
 6
 7     public static CloneableSingleton getInstance(){
 8         return INSTANCE;
 9     }
10
11     public Object clone() throws CloneNotSupportedException{
12         return super.clone();
13     }
14
15 }

第一中方法是通过clone的方式破坏单例的唯一性;Java中类通过实现Cloneable接口并复写clone方法就可以完成一个对象的拷贝。可以通过一下代码进行破坏单例。

1 public static void checkClone() throws Exception{
2         CloneableSingleton a = CloneableSingleton.getInstance();
3         CloneableSingleton b = (CloneableSingleton) a.clone();
4
5         assertEquals(a, b);
6
7 }

2、Break Singleton with Serialization

 1 public class SerializableSingleton implements Serializable{
 2
 3     private static final long serialVersionUID = 3506845333451732997L;
 4
 5     private static final SerializableSingleton INSTANCE = new SerializableSingleton();
 6     private  SerializableSingleton(){
 7
 8     }
 9
10     public static SerializableSingleton getInstance(){
11         return INSTANCE;
12     }
13 }

第二种方法是利用序列化与反序列化,当Singleton类实现了Serializable接口就代表它是可以被序列化的,该实例会被保存在文件中,需要是从该文件中读取并反序列化成对象。默认的反序列化过程是绕开构造函数直接使用字节生成一个新的对象,于是Singleton在反序列化时被创造出第二个实例。通过以下代码可以实现这一行为。

 1 public static void checkSerialization() throws Exception{
 2         File file = new File("SerializableSingleton.out");
 3         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
 4         SerializableSingleton a = SerializableSingleton.getInstance();
 5         out.writeObject(a);
 6         out.close();
 7
 8         ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
 9         SerializableSingleton b = (SerializableSingleton)in.readObject();
10         in.close();
11
12         assertEquals(a, b);
13  }

3、Break Singleton with Reflection

1 public static void checkReflection() throws Exception{
2     EagerSingleton a = EagerSingleton.getInstance();
3     Constructor<EagerSingleton> cons = EagerSingleton.class.getDeclaredConstructor();
4     cons.setAccessible(true);
5     EagerSingleton b = (EagerSingleton)cons.newInstance();
6     assertEquals(a, b);
7 }

前面两种破坏方式是通过避开私有构造函数另辟蹊径来实现的,而将私有不允许外界直接调用的,通过反射机制强行公开起访问权限。

4、Break Singleton with Classloaders

 1 public static void checkClassloader() throws Exception{
 2     String className = "fernando.lee.singleton.EagerSingleton";
 3     ClassLoader classLoader1 = new MyClassloader();
 4     Class<?> class1 = classLoader1.loadClass(className);
 5
 6     ClassLoader classLoader2 = new MyClassloader();
 7     Class<?> class2 = classLoader2.loadClass(className);
 8
 9     System.out.println("classLoader1" + class1.getClassLoader());
10     System.out.println("classLoader2" + class2.getClassLoader());
11
12     Method getInstance1 = class1.getDeclaredMethod("getInstance");
13     Method getInstance2 = class2.getDeclaredMethod("getInstance");
14
15     Object a = getInstance1.invoke(null);
16     Object b = getInstance2.invoke(null);
17
18     assertEquals(a, b);
19 }

Java中一个类并使单纯依靠其包类名来标记的,而是全包类名加上加载它的类加载器共同确定的。因此不同的类加载器加载的Singleton类并不认为是相同的,所以单例会被破坏,通过自定义编写的MyClassLoader即可实现。

由此看来,Singleton唯有妥善关闭上述的几个后门才能称得上是真正的单例。目前只了解到有两种对应措施:第一方式完善现有的实现让克隆、序列化、反射和类加载器无从下手,第二种则是通过枚举类型间接实现单例。

1、Safe Singleton

 1 public class SafeSingleton implements Serializable, Cloneable{
 2
 3     private static final long serialVersionUID = -3789939603862706007L;
 4
 5     private static SafeSingleton INSTANCE = new SafeSingleton();
 6
 7     private SafeSingleton(){
 8         if (INSTANCE != null){
 9             throw new IllegalStateException("Singleton instance Already created.");
10         }
11     }
12
13     private static SafeSingleton getInstance(){
14         return INSTANCE;
15     }
16
17     private Object readResolve() throws ObjectStreamException{
18         return INSTANCE;
19     }
20
21     public Object clone() throws CloneNotSupportedException{
22         throw new CloneNotSupportedException("Singleton can't be cloned");
23     }
24 }

在原有的Singleton的基础上完善若干方法即可实现一个更安全的更为纯正的Singleton。当实例已经存在时试图通过调用私有构造函数会直接报错,直接抵御了反射机制的入侵;让调用clone方法直接报错避免了实例被克隆;复写readReslove方法直接返回现有的实例本身可以防止反序列化过程中生成新的实例。

2、Enum Singleton

 1 public enum EnumSingleton{
 2
 3     YuLe("T39487342342344", "YuLe", "娱乐"),
 4     KeJi("T45345345345345", "KeJi", "科技"),
 5     TiYu("T53453242435455", "TiYu", "体育");
 6
 7     private String type;
 8     private String eName;
 9     private String desc;
10
11     private EnumSingleton(String type, String eName, String desc){
12         this.type = type;
13         this.eName = eName;
14         this.desc = desc;
15     }
16
17     public String getType() {
18         return type;
19     }
20
21     public void setType(String type) {
22         this.type = type;
23     }
24     public String getEname() {
25         return eName;
26     }
27     public void setEname(String eName) {
28         this.eName = eName;
29     }
30     public String getDesc() {
31         return desc;
32     }
33     public void setDesc(String desc) {
34         this.desc = desc;
35     }
36
37     public String toString(){
38         return this.desc + " " + this.eName + " " + this.type;
39     }
40 }

采用枚举的方式实现的非常的简单,可以直接通过EnumSingleton.TuLe获取该实例。Java中所有定义为enum的类内部都继承了Enum类,而Enum具备的特性包括类加载是静态的来保证线程安全,而且其中clone方法是final的直接会抛出CloneNotSupportedException一场因而不具备拷贝,同时与生俱来的序列化机制也是直接交由JVM掌握的并不会创建出新的实例,此外Enum不能被显式实例化反射破坏也不起作用。缺陷就是既然继承了Enum类,就不可以再集成其他的类,这是由Java的单集成模式限制的。所以,除非是有非常有用,否则不会仅仅为了一个纯的Singleton就把class变成enum。

转载于:https://www.cnblogs.com/Janey870103/p/9239818.html

Java-设计模式之单例模式相关推荐

  1. Java设计模式之单例模式(七种写法)

    Java设计模式之单例模式(七种写法) 第一种,懒汉式,lazy初始化,线程不安全,多线程中无法工作: public class Singleton {private static Singleton ...

  2. Java设计模式之单例模式的学习

    本篇是本人的第二篇博客 旨在记录本人对于Java设计模式之单例模式的学习和理解,也希望本篇可以对一些正在学习的小伙伴起到一些帮助 单例模式(singleton)的特点: 1.单例模式有且仅有一个实例: ...

  3. java设计模式之单例模式(七种方法)

    单例模式:个人认为这个是最简单的一种设计模式,而且也是在我们开发中最常用的一个设计模式. 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个 ...

  4. Java 设计模式(3)单例模式

    前言 概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自 ...

  5. java设计模式之——单例模式(八种实现)

    一.介绍 有时,我们需要某个类的实例始终只有一个,举个例子,如果用面向对象语言写的操作系统,那么桌面这个实例肯定就只有一个,无论从哪个地方进入的桌面,都是同一个. 所谓类的单例设计模式,就是采取一定的 ...

  6. Java设计模式之单例模式(Singleton Pattern)

    **单例模式:用来创造独一无二的,只能有一个实例的对象设计模式.单例模式确保一个类只有一个实例,并提供一个全局访问点.**相比于全局变量(对对象的静态引用),单例模式可以延迟实例化,而且全局变量不能保 ...

  7. Java设计模式:单例模式

    学而时习,稳固而之心, 好久没有复习java的知识了,今天有空温习了单例模式,这里记录一下 单例模式是常见的设计模式的一种,其特点就是 指一个类只有一个实例,且该类能自行创建这个实例  , 保证一个类 ...

  8. Java 设计模式之单例模式

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创建自己的对 ...

  9. Java设计模式(二) -- 单例模式

    单例模式是Java中最广泛应用的设计模式之一,为创建对象提供了一种绝佳的方式.因此,在一些Java程序中, 一些管理器和控制器经常被设计为单例模式. 这种模式涉及到一个单一的类,该类负责创建自己的对象 ...

  10. java设计模式学习 ----- 单例模式(Singleton)

    单例模式(Singleton) 单例对象(Singleton)是一种经常使用的设计模式. 在Java应用中,单例对象能保证在一个JVM中,该对象仅仅有一个实例存在.单例模式也分三种:懒汉式单例.饿汉式 ...

最新文章

  1. C++ 各种构造函数
  2. java 最少使用(lru)置换算法_「面试」LRU了解么?看看LinkedHashMap如何实现LRU算法...
  3. 初学者如何选出最适合自己深度学习框架?
  4. get请求报500_http请求和响应的全过程
  5. [华清远见]FPGA公益培训
  6. select默认下拉箭头改变、option样式清除
  7. 【入门6】函数与结构体(今天刷洛谷了嘛)
  8. Java之BIO NIO AIO区别联系
  9. 更改tomcat的根目录路径
  10. iOS相同字符串保存地址唯一
  11. BestCoder 2nd Anniversary 1004Hdu 5721 Palace
  12. python1e2_Python必修基础(1)
  13. 维护IBM DB2数据库所应了解的根蒂基本常识-9
  14. goland编写go语言导入自定义包出现: package xxx is not in GOROOT (/xxx/xxx) 的解决方案
  15. 数字信号处理教程答案及解析(第五版)
  16. python通过线程实现定时器timer的方法
  17. 百度地图的反地址解析(通过经纬度查询地址信息)
  18. rd640服务器引导,ThinkServer RD640 OS安装手册 V1.4.pdf
  19. 用 Python 实现词云可视化
  20. docker 出现错误:no such file or directory. Are you trying to connect to a TLS-enabled daemon without TLS

热门文章

  1. html chm 64,Win7 64位下的CHM
  2. 量子计算机1003无标题,量子计算机研究
  3. java jit技术_JVM之JIT
  4. jason by gson复习
  5. 汽车SoC安全故障的自动识别(下):案例展示和指标分析
  6. android 很多牛叉布局github地址
  7. Python-裁判文书网
  8. 关于网站目录结构需要注意的地方
  9. kubeadm部署kubernetes集群
  10. Remark Holdings平安城市解决方案助力城市安全升级