JAVA设计模式之单例模式
来源:互联网 发布:淘宝怎么更改支付宝 编辑:程序博客网 时间:2024/05/21 04:22
前言:
本文旨在介绍常见的单例模式实现方式,以及如何破解单例模式和防止破解,最后对比几种模式的调用效率。
一、常见的单例模式种类
1、饿汉模式
其一:在Java底层类加载机制中,static变量会在类加载时初始化,此时不会涉及多个线程对象访问该对象;
JVM保证只会装载一次该类,即不会发生并发访问的问题,因此不需要synchronized关键字。但是如果在写业务逻辑
时只是加载本类,而不是调用获取对象的方法,这样的模式会造成资源浪费。
其二:因为在类初始化时就加载了该类对象,即不存在延时加载,但是平常工作中可能更多的需要延时加载;
其三:调用效率高。
/** * @desc 饿汉模式 * @author 木子Lee * @time 2017年9月21日21:28:43 * @see jdk1.8 */public class Singleton01 { private static Singleton01 instance = new Singleton01(); private Singleton01(){} public static Singleton01 getInstance(){ return instance; }}
2、懒汉模式
其一:延时加载,即真正使用的时候才加载;
其二:资源利用率高,但是每次调用getInstance()方法都需要同步,并发效率低;
其三:因其方法需要同步,调用效率低。
/** * @desc 懒汉模式 * @author 木子Lee * @time 2017年9月21日22:01:27 * @see jdk1.8 */public class Singleton02 { private static Singleton02 instance; private Singleton02(){} public static synchronized Singleton02 getInstance(){ if(instance==null){ instance=new Singleton02(); } return instance; }}
3、 双重检测锁机制实现
其一:将内容同步到if内部,提高执行效率,不需要每次都同步获取对象,即只有第一次才同步;
其二:编译器优化以及Java虚拟机JVM底层内部模型暂时不能完美的支持此模式,不建议使用。
/** * @desc 双重检测锁机制模式 * @author 木子Lee * @time 2017年9月21日22:10:45 * @see jdk1.8 */public class Singleton03 { private static Singleton03 instance=null; private Singleton03(){} public static Singleton03 getInstance(){ if(instance==null){ Singleton03 s; synchronized(Singleton03.class){ s=instance; if(s==null){ synchronized (Singleton03.class) { if(s==null){ s=new Singleton03(); } } instance=s; } } } return instance; }}
4、静态内部类模式
其一:因外部类没有static属性,则不会像饿汉模式那样立即加载对象,即是一种懒加载方式;
其二:只有调用真正的getInstance()才会加载静态内部类。由于JAVA类加载机制在加载内部类时是线程安全的。
其三:instance是static final类型,保证了JVM内存中只有一个实例的存在,而且只能被赋值一次,即保证了线程安全
其四:兼备了并发高效和延时加载的优势,建议使用。
/** * @desc 静态内部类模式 * @author 木子Lee * @time 2017年9月21日22:22:15 * @see jdk1.8 */public class Singleton04 { private Singleton04(){} private static class Singleton04Classinstance{ private static final Singleton04 instance=new Singleton04(); } public static Singleton04 getInstance(){ return Singleton04Classinstance.instance; }}
5、枚举模式
其一:Java中枚举本身就是单例;
其二:由于JVM提供保障,从而避免了通过反射和反序列化漏洞;
其三:没有延时加载。
/** * @desc 枚举模式 * @author 木子Lee * @time 2017年9月21日22:31:29 * @see jdk1.8 */public enum Singleton05 { //定义枚举元素,即代表单例实例 INSTANCE; public void singletonOperation(){ //单例自己的操作,即功能处理 }}
二、反射、序列化和反序列化如何破解单例(除枚举模式)
1、反射破解懒汉式
/*** 通过Java反射破解懒汉式单例,一般情况下只是需要写项目不考虑防止破解;但是写产品时需要考虑这点 * @param args* @throws ClassNotFoundException* @throws NoSuchMethodException* @throws SecurityException* @throws InstantiationException* @throws IllegalAccessException* @throws IllegalArgumentException* @throws InvocationTargetException */public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Singleton02 S1=Singleton02.getInstance(); Singleton02 S2=Singleton02.getInstance(); System.out.println(S1); System.out.println(S2); Class<Singleton02> clazz=(Class<Singleton02>)Class.forName("test.Singleton02"); Constructor<Singleton02> c=clazz.getDeclaredConstructor(null); c.setAccessible(true); Singleton02 s3 =c.newInstance(); Singleton02 s4 =c.newInstance(); System.out.println(s3); System.out.println(s4); }
注:运行结果如下
S1:test.Singleton02@15db9742S2:test.Singleton02@15db9742S3:test.Singleton02@6d06d69cS4:test.Singleton02@7852e922
可以看出S1和S2是同一个对象实例,但是S3和S4不再是同一个对象实例了。
2、如何防止反射破解
可以在实现单例类的私有化构造器中抛出运行时异常等操作,即在懒汉式中对其构造器做如下修改:
private Singleton02(){ if(instance!=null){ throw new RuntimeException(); } }
打印抛出异常:
test.Singleton02@15db9742test.Singleton02@15db9742Exception in thread "main" 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 Singleton05.main(Singleton05.java:43)Caused by: java.lang.RuntimeException at test.Singleton02.<init>(Singleton02.java:15) ... 5 more
建议:一般写项目的情况下不需要考虑防止类似破解,但是写诸如Spring等产品时需要考虑类似破解。
3、序列化和反序列化破解
其一:对于防止的单例类需要实现序列化接口
测试代码:
/*** 以懒汉式为例通过序列化和反序列化破解单例* @param args* @throws IOException * @throws ClassNotFoundException */public static void main(String[] args) throws IOException, ClassNotFoundException { Singleton02 s1=Singleton02.getInstance(); Singleton02 s2=Singleton02.getInstance(); System.out.println(s1); System.out.println(s2); FileOutputStream out=new FileOutputStream("e:/a.txt"); ObjectOutputStream oos= new ObjectOutputStream(out); oos.writeObject(s1); //仅仅做测试,就不做严密的写法 oos.close(); out.close(); ObjectInputStream in=new ObjectInputStream(new FileInputStream("e:/a.txt")); Singleton02 s3=(Singleton02)in.readObject(); System.out.println(s3);}
运行结果如下:
S1:test.Singleton02@15db9742S2:test.Singleton02@15db9742S3:test.Singleton02@4c873330
4、防止序列化和反序列化破解
需要在单例类中加入如下方法:
private Object readResolve() throws ObjectStreamException{ return instance;}
注:加入该方法后,反序列化时直接返回指定的该对象实例,不再创建新的对象实例,从而防止序列化和反序列化破解
防止反射和序列化破解的完整单例类方法代码:
/** * @desc 懒汉模式 * @author 木子Lee * @time 2017年9月21日22:01:27 * @see jdk1.8 */public class Singleton02 implements Serializable{ private static Singleton02 instance; private Singleton02(){ if(instance!=null){ throw new RuntimeException(); } } public static synchronized Singleton02 getInstance(){ if(instance==null){ instance=new Singleton02(); } return instance; } private Object readResolve() throws ObjectStreamException{ return instance; }}
5、测试几种单例模式的调用效率
/*** 测试多线程环境下各种模式耗时* @param args* @throws Exception */public static void main(String[] args) throws Exception{ long startTime=System.currentTimeMillis(); System.out.println(startTime); int threadCount=10; final CountDownLatch countDownLatch=new CountDownLatch(threadCount); for(int i=0;i<threadCount;i++){ new Thread(new Runnable() { @Override public void run() { for(int i=0;i<100000;i++){ Object o=Singleton01.getInstance(); } countDownLatch.countDown(); } }).start(); } countDownLatch.await(); long endTime=System.currentTimeMillis(); System.out.println("总耗时:"+(endTime-startTime));}
说明:本次测试仅仅在本次特定的环境下,不同环境得出的数值可能不同
各种模式运行结果:
饿汉式:12
懒汉式:57
静态内部类:16
枚举类:10
双重检测锁:8
- java设计模式之单例模式
- Java模式设计之单例模式
- Java模式设计之单例模式
- Java模式设计之单例模式
- java设计模式之单例模式
- Java模式设计之单例模式
- Java模式设计之单例模式
- Java设计模式之单例模式
- Java设计模式之单例模式
- Java设计模式之单例模式
- Java设计模式之单例模式
- Java模式设计之单例模式
- java设计模式之单例模式
- java设计模式之单例模式
- java设计模式之单例模式
- java设计模式之单例模式
- java设计模式之单例模式
- java设计模式之单例模式
- 学习笔记:ES6之Promise
- Linux内核进程调度
- Redis集群方案大全
- 前端性能优化+CDN优化
- datagrid控制分页js代码
- JAVA设计模式之单例模式
- Solr的安装及配置
- 极光推送服务器端代码(java服务器后台向手机端自定义推送消息)
- XOR加密--PHP版
- SpringMVC的Controller接口方法参数解析
- 编辑页面日期后显示多个0
- list,tuple,dict,set的区别和用法
- webrtc 服务器搭建(1)apprtc房间服务器
- MFC的消息机制