设计模式之单例模式
Posted blue星空
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之单例模式相关的知识,希望对你有一定的参考价值。
0. 单例模式准备
0.1 单例模式的作用
0.1.1 一个类只有一个实例
0.1.2 该实例能够通过一个全局的方法获取到
0.2 单例模式适用场景:
0.2.1 多线程之间操作一个对象【共享一个资源】
0.2.2 性能优化,减少创建对象的消耗【创建时间、对象所占的空间】
0.3 单例模式的特点
0.3.1 具有私有的构造器、一个静态变量、一个静态方法、[一个静态内部类]
1 饿汉模式
1.1本质: 在类加载的时候就创建实例,需要获取实例时直接返回已创建的实例
1.2 优点:线程安全
1.3 缺点: 类加载的时候就创建实例,浪费空间
1.4 代码示例:
package com.blueStarWei.pattern; public class HungrySingleton { //create instance when loading class private static HungrySingleton singleton = new HungrySingleton();
//将构造器私有化,防止外界通过该构造器创造多个实例 private HungrySingleton(){}
public static HungrySingleton getInstance(){ return singleton; } }
【为了减省空间的浪费,因此人们想到了在需要获取实例的时候创建实例,因此产生了懒汉模式】
2. 懒汉模式
2.1 本质: 当需要获取实例的时候,先进行判断,如果实例不存在,就创建实例,否则直接返回已存在的实例【避免了在加载类的同时直接创建实例】
2.2 懒汉模式1.0版本
package com.blueStarWei.pattern; public class LazySingleton { private static LazySingleton singleton;
private LazySingleton(){} public static LazySingleton getInstance(){ if(singleton == null){ singleton = new LazySingleton(); } return singleton; } }
2.2.1 优点:节省了空间
2.2.2 缺点: 线程不安全【如果多个线程同时执行if判断,因为多个线程获取的singleton1都是空,所以会创建多个实例】
【为了避免懒汉模式的线程不安全问题,人们想到了对getInstance()添加synchronized修饰符,于是产生了懒汉模式1.1版本】
2.3 懒汉模式1.1版本
package com.blueStarWei.pattern; public class LazySingleton1 { private static LazySingleton1 singleton;
private LazySingleton1(){} public static synchronized LazySingleton1 getInstance(){ if(singleton == null){ singleton = new LazySingleton1(); } return singleton; } }
2.3.1 优点: 节省空间、线程安全
2.3.2 缺点: 性能不好【synchronized会造成线程阻塞】
【为了避免线程阻塞的出现,人们又想到了双重校验锁,因此产生了懒汉模式1.2版本】
2.4 懒汉模式1.2版本【双重检查枷锁方式/double-check】
package com.blueStarWei.pattern; public class LazySingleton2 {
//使用volatile阻止命令重排,保证多线程正确处理singleton变量 private volatile static LazySingleton2 singleton;
private LazySingleton2(){} public static LazySingleton2 getInstance(){ if(singleton == null){
//该部分只会被执行一次 synchronized (LazySingleton1.class) { if(singleton == null){ singleton = new LazySingleton2(); } } } return singleton; } }
2.4.1 优点: 节省空间,线程安全,性能相对有优化【只是优化了已经存在实例的情况,即:当实例已经被创建,再调用getInstance()的时候就不会经过synchronized修饰符】
2.4.2 缺点:使用了synchronized修饰符,线程仍然会有阻塞情况的发生。
【为了避免使用synchronized修饰符,人们想到了静态内部类(因为一个类被加载,当且仅当该类的某个静态成员被调用),于是懒汉模式2.0时代到来】
2.5 懒汉模式2.0版本【推荐版本】
package com.blueStarWei.pattern; /** * 因为对象实例化是在内部类加载时构建,因此是线程安全的【毕竟类只加载一次嘛】 * @author HuWei * */ public class LazySingleton3 {
private LazySingleton3(){} //当getInstance()方法第一次被调用时,内部类被加载 static class SingletonHolder { private static final LazySingleton3 singleton3 = new LazySingleton3(); } //没有使用synchronized修饰符,避免了性能损耗 public static LazySingleton3 getInstance() { return SingletonHolder.singleton3; } }
2.5.1 优点: 节省空间、线程安全、避免性能的额外损耗
2.5.2 缺点: 在反射作用下,单例模式会被破坏
【为了防止反射破坏单例模式,所以产生了单例模式3.0版本】
2.6 单例模式3.0版本
2.6.1 版本代码
package com.blueStarWei.pattern; public class LazySingleton4 { /* -------添加部分 start-------- * 作用:第二次调用构造函数(即:创建第二个实例)时抛出异常 * */ private static boolean initialized = false; private LazySingleton4() { synchronized (LazySingleton4.class) { if(initialized == false){ initialized = !initialized; }else{ throw new RuntimeException("Singleton has been broken!"); } } } /* -------添加部分 end-------- */ //当getInstance()方法第一次被调用时,内部类被加载 static class SingletonHolder { private static final LazySingleton4 singleton4 = new LazySingleton4(); } //没有使用synchronized修饰符,避免了性能损耗 public static LazySingleton4 getInstance() { return SingletonHolder.singleton4; } }
2.6.1.1 版本Demo
package com.blueStarWei.pattern; import java.lang.reflect.Constructor; /** * 通过反射调用时会报错 * Caused by: java.lang.RuntimeException: Singleton has been broken! * @author HuWei * */ public class LazySingleton4Demo { public static void main(String[] args) { //通过getInstance()创建实例 LazySingleton4 sington = LazySingleton4.getInstance(); //通过反射创建实例 LazySingleton4 sington4 = null; try { Class<LazySingleton4> clazz = LazySingleton4.class;
Constructor<LazySingleton4> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); sington4 = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("getInstance() : "+sington); System.out.println("invoke : "+sington4); } }
2.6.1.2 Demo日志
java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) at java.lang.reflect.Constructor.newInstance(Unknown Source) at com.blueStarWei.pattern.LazySingleton4Demo.main(LazySingleton4Demo.java:21) Caused by: java.lang.RuntimeException: Singleton has been broken! at com.blueStarWei.pattern.LazySingleton4.<init>(LazySingleton4.java:19) ... 5 more getInstance() : com.blueStarWei.pattern.LazySingleton4@7852e922 getInstance() : com.blueStarWei.pattern.LazySingleton4@7852e922 invoke : null
2.6.2 反射破环单例模式示例
package com.blueStarWei.pattern; import java.lang.reflect.Constructor; /** * 不难发现,通过getInstance()和反射创建的实例的内存地址不一样,即不是同一个实例对象 * @author HuWei * */ public class LazySingleton3Demo { public static void main(String[] args) { //通过getInstance()创建实例 LazySingleton3 sington = LazySingleton3.getInstance(); LazySingleton3 sington2 = LazySingleton3.getInstance(); //通过反射创建实例 LazySingleton3 sington3 = null; try { Class<LazySingleton3> clazz = LazySingleton3.class; Constructor<LazySingleton3> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); sington3 = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("getInstance() : "+sington); //getInstance() : com.blueStarWei.pattern.LazySingleton3@15db9742 System.out.println("getInstance() : "+sington2); //getInstance() : com.blueStarWei.pattern.LazySingleton3@15db9742 System.out.println("invoke : "+sington3); //invoke : com.blueStarWei.pattern.LazySingleton3@6d06d69c } }
【在分布式系统中,单例类有时需要实现Serializable接口。以上版本通过序列和反序列化产生的实例将会不是同个实例对象,所以产生了懒汉模式4.0版本】
2.7 懒汉模式4.0版本
2.7.1 Demo代码
package com.blueStarWei.pattern; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; public class LazySingleton4Demo2 { public static void main(String[] args) { LazySingleton4 singleton = LazySingleton4.getInstance(); try { ObjectOutput out = new ObjectOutputStream(new FileOutputStream("fileName.txt")); out.writeObject(singleton); out.close(); //deserializable from file to object ObjectInput in = new ObjectInputStream(new FileInputStream("fileName.txt")); LazySingleton4 singleton2 = (LazySingleton4)in.readObject(); in.close(); System.out.println("Serializable : "+singleton); System.out.println("Deserializable : "+singleton2); } catch (Exception e) { e.printStackTrace(); } } }
2.7.2 反序列返回不同的对象示例
public class LazySingleton4 implements Serializable
2.7.2.1 Demo日志
Serializable : com.blueStarWei.pattern.LazySingleton4@33909752
Deserializable : com.blueStarWei.pattern.LazySingleton4@776ec8df
2.7.3 版本代码
package com.blueStarWei.pattern; import java.io.Serializable; public class LazySingleton4 implements Serializable{ private static final long serialVersionUID = 1L; private static boolean initialized = false;
private LazySingleton4() { synchronized (LazySingleton4.class) { if(initialized == false){ initialized = !initialized; }else{ throw new RuntimeException("Singleton has been broken!"); } } } static class SingletonHolder { private static final LazySingleton4 singleton4 = new LazySingleton4(); } public static LazySingleton4 getInstance() { return SingletonHolder.singleton4; } /* ----添加部分 start---- */ /** * 反序列化的结果仍然通过getInstance()获取 */ private Object readResolve(){ return getInstance(); } /* ----添加部分 end---- */ }
2.7.3.1 Demo日志
Serializable : com.blueStarWei.pattern.LazySingleton4@33909752
Deserializable : com.blueStarWei.pattern.LazySingleton4@33909752
3 其它问题
3.1 如果存在多个类加载器,可能会产生多个单例并存的现象。
3.1.1 原因: 每个类加载器都定义了一个命名空间,如果存在多个类加载器,不同的类加载器可能会加载同一个类,所以同一类会被加载多次。
3.1.2 解决方法: 自行指定类加载器,并指定同一个类加载加载器。
3.2 单例类不能被继承
3.2.1 原因: 继承单例类必须将单例类构造器的访问修饰符改成public/protected,这样会导致其他类也可以实例化该单例类,破坏了单例模式
3.2.2 解决方法: 不继承单例类
3.3 为何全局变量会比单例模式差?
3.3.1 全局变量基本上就是对对象的静态引用,全局变量虽然能提供全局访问,但是不能保证只有一个实例。
4 总结
实际项目中,一般从懒汉模式2.0、3.0、4.0版本中选择一种即可
更多内容,请访问:http://www.cnblogs.com/BlueStarWei/
以上是关于设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章