思维和思考的方式才是最重要的,语言只是一种工具。

GOF23

Group of Four——国外的四个软件大牛总结出来的模式。
创建型模式:帮助我们创建对象


单例模式

单例模式的常见使用场景:

单例模式保证一个类只有一个示例,并且提供一个访问该实例的全局访问点。
由于单例模式只生成一个实例,减少了系统性能开销

常见的五种单例模式实现方式

– 主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)

– 其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举式(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞!)

如何选用?

– 单例对象 占用 资源 少,不需要 延时加载:
• 枚举式 好于 饿汉式

– 单例对象 占用 资源 大,需要 延时加载:
• 静态内部类式 好于 懒汉式


常见应用场景

– Windows的Task Manager(任务管理器)就是很典型的单例模式
– windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
– 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
– 网站的计数器,一般也是采用单例模式实现,否则难以同步。
– 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
– 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
– 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
– Application 也是单例的典型应用(Servlet编程中会涉及到)
– 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
– 在servlet编程中,每个Servlet也是单例
– 在spring MVC框架/struts1框架中,控制器对象也是单例

常见的五种单例模式实现方式

(1)饿汉式

线程安全,调用效率高

package cn.hanquan.pattern;
/** 饿汉式单例模式*/
public class PatternTest {// 类加载器初始化的时候,对象立即加载出来// 天然的线程安全,不需要synchronize,调用效率高private static PatternTest instance = new PatternTest();private PatternTest() {// 私有的构造器}public static PatternTest getInstance() { // 唯一公开的方法return instance;}
}

调用:PatternTest s = PatternTest.getInstance()

package cn.hanquan.pattern;
/** 单例对象的使用*/
public class Client {public static void main(String[] args) {PatternTest s1 = PatternTest.getInstance();PatternTest s2 = PatternTest.getInstance();// 无论创建、调用多少次,都是同一个对象System.out.println(s1);System.out.println(s2);// cn.hanquan.pattern.PatternTest@2c13da15// cn.hanquan.pattern.PatternTest@2c13da15}
}

(2)懒汉式:延迟加载

只有在真正需要用到的时候,才会去加载它,资源利用效率高。缺点是需要synchronize,调用效率低

package cn.hanquan.pattern;
/** 懒汉单例模式*/
public class PatternTest {// 类加载器初始化的时候,对象立即加载出来// 天然的线程安全,不需要synchronize,调用效率高private static PatternTest instance;private PatternTest() {// 私有化构造器}public static PatternTest getInstance() {if (instance == null) {instance = new PatternTest();}return instance;}
}

(3)双重检测锁实现单例模式

由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。实际工作用不到。

package cn.hanquan.pattern;
/** 双重检测锁实现*/
public class PatternTest {private static PatternTest instance = null;  private PatternTest() {}public static PatternTest getInstance() {if (instance == null) {PatternTest sc;synchronized (PatternTest.class) {sc = instance;if (sc == null) {synchronized (PatternTest.class) {if (sc == null) {sc = new PatternTest();}}instance = sc;}}}return instance;}
}

(4)静态内部类实现方式

也是一种懒加载方式

– 外部类没有static属性,则不会像饿汉式那样立即加载对象。
– 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。 instance是static final
类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
– 兼备了并发高效调用和延迟加载的优势!

package cn.hanquan.pattern;/** 双重检测锁实现*/
public class PatternTest {// 使用静态内部类private static class ClassInstance {private static final PatternTest instance = new PatternTest();}private PatternTest() {}public static PatternTest getInstance() {return ClassInstance.instance;}
}

(5)使用枚举实现单例模式

枚举是天然的单例模式

• 优点:
– 实现简单
– 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!

• 缺点:
– 无延迟加载

package cn.hanquan.pattern;
/** 双重检测锁实现*/
public enum PatternTest {// 枚举元素本身就是单利对象// 调用的时候:PatternTest.INSTANCEINSTANCE;public void operation() {// 可以加入其它操作}
}

防止反射漏洞

(反射、反序列化对枚举是无效的)

可以使用反射,直接调用私有的构造器,破解单例模式

  • 被破解的单例模式PatternTest.java
package cn.hanquan.pattern;
/** 饿汉式单例模式*/
public class PatternTest {private static PatternTest instance = new PatternTest();private PatternTest() {// 私有的构造器}public static PatternTest getInstance() { // 唯一公开的方法return instance;}
}
  • Client2.java利用反射,破解单例模式(注意一下18行的constructor.setAccessible(true),用于取消private检查)

可以看到,20行和21行,直接调用私有的构造器,获得不同的对象。

package cn.hanquan.pattern;import java.lang.reflect.Constructor;public class Client2 {public static void main(String[] args) throws Exception {PatternTest s1 = PatternTest.getInstance();PatternTest s2 = PatternTest.getInstance();// 无论创建、调用多少次,都是同一个对象System.out.println(s1);// cn.hanquan.pattern.PatternTest@2c13da15System.out.println(s2);// cn.hanquan.pattern.PatternTest@2c13da15// 利用反射破解单例模式Class<PatternTest> clazz = (Class<PatternTest>) Class.forName("cn.hanquan.pattern.PatternTest");Constructor<PatternTest> constructor = clazz.getDeclaredConstructor(null);// 获得构造器constructor.setAccessible(true);// 取消private检查PatternTest s3 = constructor.newInstance();PatternTest s4 = constructor.newInstance();// 直接调用私有构造器,获得不同的对象System.out.println(s3);// cn.hanquan.pattern.PatternTest@77556fdSystem.out.println(s4);// cn.hanquan.pattern.PatternTest@368239c8}
}

那么,如何避免别人通过反射破解单例呢?

  • PatternTest.java增强版:多次调用时,手动抛出异常
package cn.hanquan.pattern;
/** 饿汉式单例模式*/
public class PatternTest {private static PatternTest instance = new PatternTest();private PatternTest() {// 私有的构造器if (instance != null) {throw new RuntimeException();}}public static PatternTest getInstance() { // 唯一公开的方法return instance;}
}

再次运行Client2.java,抛出如下异常:

这样,成功阻止了:通过反射破解单例模式。
一般情况下,在开发时,不需要考虑这么多。因为我们写的是项目,而不是JDK…


防止反序列化漏洞

(反射、反序列化对枚举是无效的)

通过反序列化的方式,构造多个对象
注意,被序列化的对象需要添加implements Serializable

  • 被破解的单例模式:PatternTest.java
package cn.hanquan.pattern;
import java.io.Serializable;/** 饿汉式单例模式*/
public class PatternTest implements Serializable{private static PatternTest instance = new PatternTest();private PatternTest() {// 私有的构造器if (instance != null) {throw new RuntimeException();}}public static PatternTest getInstance() { // 唯一公开的方法return instance;}
}
  • Client2.java使用反序列化破解单例模式,创建新的对象
package cn.hanquan.pattern;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;// 通过反序列化破解单例模式,构造多个不同的对象
public class Client2 {public static void main(String[] args) throws Exception {PatternTest s1 = PatternTest.getInstance();PatternTest s2 = PatternTest.getInstance();// 无论创建、调用多少次,都是同一个对象System.out.println(s1);// cn.hanquan.pattern.PatternTest@2c13da15System.out.println(s2);// cn.hanquan.pattern.PatternTest@2c13da15// 序列化FileOutputStream fos = new FileOutputStream("c:/picture/test.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s1);oos.close();fos.close();// 反序列化ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:/picture/test.txt"));PatternTest s3 = (PatternTest) ois.readObject();System.out.println(s3);// cn.hanquan.pattern.PatternTest@6fffcba5}
}

那么,使用单例模式时,如何避免反序列化破解?

  • PatternTest.java避免反序列化破解

反序列化会自动调用readResolve()方法。

反序列化时,如果定义了readResolve()方法,则直接返回此方法指定的对象。而不需要单独创建新对象!

package cn.hanquan.pattern;import java.io.ObjectStreamException;
import java.io.Serializable;/** 饿汉式单例模式*/
public class PatternTest implements Serializable {private static PatternTest instance = new PatternTest();private PatternTest() {// 私有的构造器if (instance != null) {throw new RuntimeException();}}public static PatternTest getInstance() { // 唯一公开的方法return instance;}// 反序列化时,如果定义了readResolve()方法,则直接返回此方法指定的对象。而不需要单独创建新对象!private Object readResolve() throws ObjectStreamException {return instance;}
}

这样,Client.java的输出为三个相同的对象:

cn.hanquan.pattern.PatternTest@2c13da15
cn.hanquan.pattern.PatternTest@2c13da15
cn.hanquan.pattern.PatternTest@2c13da15

说明成功避免了利用反序列化破解。


五种单例模式的效率测试

饿汉式:22ms
懒汉式:636ms
静态内部类式:28ms
枚举式:32ms
双重检查锁式:65ms

使用CountDownLatch等待其他线程

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

countDown() 当前线程调此方法,则计数减一(建议放在 finally里执行)
await(), 调用此方法会一直阻塞当前线程,直到计时器的值为0

package com.bjsxt.singleton;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.concurrent.CountDownLatch;/*** 测试多线程环境下五种创建单例模式的效率* @author 尚学堂高淇 www.sxt.cn**/
public class Client3 {public static void main(String[] args) throws Exception {long start = System.currentTimeMillis();int threadNum = 10;final CountDownLatch  countDownLatch = new CountDownLatch(threadNum);for(int i=0;i<threadNum;i++){new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<1000000;i++){//                      Object o = SingletonDemo4.getInstance();Object o = SingletonDemo5.INSTANCE;}countDownLatch.countDown();}}).start();}countDownLatch.await();   //main线程阻塞,直到计数器变为0,才会继续往下执行!long end = System.currentTimeMillis();System.out.println("总耗时:"+(end-start));}
}

【Java设计模式】GOF32 - 单例模式相关推荐

  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. POJ1201基础差分约束
  2. Linux Graphic DRI Wayland 显示子系统
  3. CSS鼠标响应事件经过、移动、点击示例介绍
  4. IE8“开发人员工具”使用详解上(各级菜单详解)
  5. 贝叶斯网络结构学习方法
  6. 解析Linux内核的基本的模块管理与时间管理操作---超时处理【转】
  7. spark笔记之RDD常用的算子操作
  8. html css 博客园,分享几个博客园代码样式的CSS配置(复制黏贴即可)
  9. iphone修改imei_iPhone这些隐藏代码你肯定不知道
  10. fx5800p编程教程_fx5800P编程计算器操作方法.pdf
  11. 爬取王者荣耀网站所有英雄皮肤图片
  12. 微信小程序(微信支付回调函数)
  13. 摄像头录像时出现连接错误
  14. 查最近一条数据SQL(多条记录时)
  15. Pytorch + Win10系统 + pip安装+ CUDA9.1版本(安装CUDA10.2版本)
  16. C++ std::set<>是什么 怎么用 遍历
  17. 主流图数据库对比,Neo4j、ArangoDB、OrientDB、JanusGraph、HugeGraph
  18. C65升级与补丁 V25->V43DIY全程
  19. 安卓app开发工具_最新app制作软件汇总:从零开始教你完成app开发
  20. 中文语音数据 - THCHS-30 : A Free Chinese Speech Corpus 【❤️下载介绍❤️】

热门文章

  1. bootstrap 图片预览_教你简单用Photoshop制作GIF图片
  2. UVa712 S-Trees满二叉树
  3. linux设置光标位置,linux下光标定位和输出颜色设置
  4. dede config.chche.inc.php,dede/config.php · 辉辉菜/三强源码 - Gitee.com
  5. 10.IDA-基本操作
  6. SQL SERVER中强制类型转换cast和convert的区别
  7. ODBC学习(一)基本理论
  8. 计算机网络 | 传输层 :UDP与TCP协议详解
  9. scrapy 解决Redirecting 301 302重定向问题
  10. Python 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。