一、概念

《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《设计模式》一书),由 Erich Gamma、Richard Helm、Ralph Johnson

和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为"四人组(Group of Four)"。

创建型模式(5个):单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式;

结构型模式(7个):适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式;

行为模式(11个):模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

1、单例模式

核心:保证一个类有且只有一个对象(实例),并且提供一个访问该实例的全局访问点。

应用场景:Windows 的任务管理器(Task Manager);

Windows的回收站(Recycle Bin),在整个系统运行中,回收站一直维护着仅有的一个实例;

项目中,读取配置文件类,一般也只有一个类,没必要每次使用配置文件的数据,每次new一个对象去读取;

网站的计数器,一般也是采用单例模式,否则难以同步;

应用程序的日志,也是采用单例模式,这是由于共享文件的日志文件一直处于打开状态,因为只有一个实例去操作,否则内容不好追加;

数据库的连接池也是采用单例模式,因为数据库的连接的是一种数据库资源;

操作系统的文件系统,也是采用单例模式,因为一个操作系统只有一个文件系统;

Application 也是典型的单例模式;

在Spring中,每个Bean默认就是单例,优点是Spring容器都可以管理;

在servlet(Server Applet:小服务程序或服务连接器,Java编写的服务器端程序),每个servlet也是单例

在Spring MVC框架/struts1框架中,控制对象也是单例。

优点: 由于单例只生成一个示例,减少了系统的性能开销,当一个对象产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接

产生一个单例对象,然后永久驻留内存的方式来解决。单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理

5种实现方式:

主要有2种:

(1) 饿汉式(线程安全、调用效率,但是不能延时加载)

(2) 懒汉式(线程安全、调用效率不高,但是可以延时加载)

次要有3种:

(3)双重检测模式(由于JVM底层内部模型的原因,偶尔会出现问题,不建议使用)

(4)静态内部类式(线程安全、调用效率高,可以延时加载)

(5)枚举单例(线程安全、调用效率高,不能延时加载,但是可以防止反射和反序列化漏洞)

如何选用?

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

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

【初步认识】

/***
* 23-1:单例模式
* 主要有2种:
(1) 饿汉式(线程安全、调用效率,但是不能延时加载) (2) 懒汉式(线程安全、调用效率不高,但是可以延时加载) 次要有3种: (3)双重检测模式(由于JVM底层内部模型的原因,偶尔会出现问题,不建议使用) (4)静态内部类式(线程安全、调用效率高,可以延时加载) (5)枚举单例(线程安全、调用效率高,不能延时加载)
*/
package cn.sxt.pattern; /* 1)饿汉式(单例对象立即加载)
* static变量会在类加载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生
* 并发访问的问题。因此synchronized关键字可以省略。
* 问题:如果只是加载本类,而不是调用getInstance(),甚至永远没有调用,则会造成资源浪费。
* */
class Singleton{
//第2步:私有化一个静态对象。类初始化时立即加载这个对象instance
private static Singleton instance=new Singleton();//instance没什么特殊含义,用s也行,实例化对象的名字 private Singleton() { //第1步:私有构造器,只有自己可以用 }
//由于加载类时,天然的线程安全,方法不用同步,调用效率高
public static Singleton getInstance() {
return instance;
}
} /* 2)懒汉式(单例对象延时加载,不立即加载,中间用的时候才去new一个新的对象)
* 延时加载,真正用的时候才去加载!
* 问题:资源利用率高,但是,每次调用getInstance()方法时,要使用并发,效率低
* */
class Singleton02{
private static Singleton02 s;
private Singleton02() {//私有化构造器 }
//为啥加同步?因为当线程A执行到s==null后,睡觉去啦,当线程B进来后发现s也是null,会去创建一个对象,当线程A醒来之后也去创建
//一个新的对象,这样就2个对象,违反单例的定义(即一个单例类有且只有一个示例(对象))
public static synchronized Singleton02 getInstance() {
if (s==null) {
s=new Singleton02();
}
return s;
}
} /*3)双重检测锁的实现(实际工作很少用),综合了懒汉和饿汉的模式
* 这个模式将同步的内容下放到if内部,提高了执行效率,不必每次获取对象时都进行同步,只有第一次才同步,创建对象后就没必要同步了
* 问题:由于编译器优化的原因和JVM底层内部模型原因,偶尔会出问题
* */ class Singleton03{
private static Singleton03 s=null;
private Singleton03() {
} public static Singleton03 getInstance() { if (s==null) {
Singleton03 sc;
synchronized (Singleton03.class) {
sc=s;
if (sc==null) {
synchronized (Singleton03.class) {
if (sc==null) {
sc=new Singleton03();
}
}
s=sc;
}
}
}
return s;
}
} /*4) 静态内部类的实现(也是一种懒加载)
* 外部类没有static属性,则不会像饿汉式那样立即加载对象
* 只有真正调用getInstance方法时才会加载静态内部类,加载类时线程是安全的,对象s是static final类型的,保证了内存中
* 只有一个实例存在,而且只能赋值一次,从而保证了线程的安全性
* 好处:兼备了并发高效调用和延迟加载的优势
*
* */ class Singleton04{
private static class SingletonClassInstance {
private static final Singleton04 s=new Singleton04();
}
//初始化Singleton04类时不会立即初始化静态内部类SingletonClassInstance,只能用到时才会通过SingletonClassInstance.s
//去调用对象s(这里也可以看作是它的属性),延时加载
public static Singleton04 getInstance() {
return SingletonClassInstance.s;//
}
private Singleton04() { }
} /*5)枚举方式(没有懒加载)
* 优点:实现简单,枚举本身就是单例模式。由JVM从根本上提供保障,避免通过反射和反序列的漏洞
* 缺点:无法延时加载
* */
enum Singleton05{//注意定义与class的区别 //定义一个枚举元素INSTANCE,它就代表Singleton05的一个单例对象
INSTANCE; //添加自己需要的操作
public void singletonOperation() { } } public class Test_0422_Singleton {
public static void main(String[] args) {
/*getInstance是一个函数,在java中,可以用这种方式使用单例模式创建类的实例,所谓单例模式就是一个类有且只有一个实例,
不像object ob=new object();的这种方式去实例化后去使用 */
/*Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2); //输出为true*/ Singleton02 s1=Singleton02.getInstance();
Singleton02 s2=Singleton02.getInstance();
System.out.println(s1==s2); //输出为true Singleton05 s5=Singleton05.INSTANCE;
Singleton05 s6=Singleton05.INSTANCE;
System.out.println(s5==s6); //输出为true }
}

【反射和序列化破解】

/***
* 使用反射和反序列破解4种单例模式(不包含枚举,枚举基于JVM底层无法破解,最安全)
* 以懒汉式为例
*/
package cn.sxt.pattern; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor; class Singleton06 implements Serializable{
private static Singleton06 s;
private Singleton06() {//加了抛出异常的私有化构造器,可以防止破解。当线程试图创建多个对象时会抛出异常。一般不用考虑
if (s!=null) {
throw new RuntimeException();
} }
/*private Singleton06() {//没加抛出异常的私有化构造器 }*/ //为啥加同步?因为当线程A执行到s==null后,睡觉去啦,当线程B进来后发现s也是null,会去创建一个对象,当线程A醒来之后也去创建
//一个新的对象,这样就2个对象,违反单例的定义(即一个单例类有且只有一个示例(对象))
public static synchronized Singleton06 getInstance() {
if (s==null) {
s=new Singleton06();
}
return s;
} //防止通过反序列破解单例。意思是在反序列化时直接调用这个方法,通过这个方法去返回我们指导的对象s,而不是创建一个新的对象
private Object readResolve() throws Exception {//这个方法自己不用调,实现反序列化时自动调用
return s;
}
} public class Test_0422_Singleton2 {
public static void main(String[] args) throws Exception {
Singleton06 s1=Singleton06.getInstance();
Singleton06 s2=Singleton06.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2); //输出为true,s1和s2是同一对象 /*//通过反射破解单例。通过反射直接调用私有构造器
Class<Singleton06> clz = (Class<Singleton06>)Class.forName("cn.sxt.pattern.Singleton06");
Constructor<Singleton06> constructor=clz.getDeclaredConstructor(null);
constructor.setAccessible(true);//跳过权限检测,访问私有对象
Singleton06 s3=constructor.newInstance();
Singleton06 s4=constructor.newInstance();
System.out.println(s3==s4);//输出为false,显然s3和s4不是同一对象。证明跳过了单例
*/ /**序列化:将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序;
* 反序列化:从字节流创建对象的相反的过程称为反序列化。而创建的字节流是与平台无关的,在一个平台上序列化的对象可以在
* 不同的平台上反序列化。
* */
//通过反序列化破解单例
FileOutputStream fos =new FileOutputStream("D:/a.txt");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(s1);//把对象s1经由ObjectOutputStream写出到文件 D:/a.txt中
oos.close();
fos.close(); ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:/a.txt"));
Singleton06 s5=(Singleton06)ois.readObject();
System.out.println(s5);//输出结果与s1和s2不同,证明破解了单例,若加了readResolve()方法,输出结果相同 }
}

【看看效率】

/***
* 测试5种单例模式的执行效率
* 懒汉式:效率最慢
*/
package cn.sxt.pattern; import java.util.concurrent.CountDownLatch; public class Test_0422_Singleton3 {
public static void main(String[] args) throws InterruptedException {
test(); } public static void test() throws InterruptedException {
int threadNum=5;
long start=System.currentTimeMillis(); /***解决时间不准的问题:主线程(与5个线程独立)可能已经执行完毕到end处了,输出主线程的时间。但是5个线程还有没有
* 执行完毕的,达不到效果。
* CountDownLatch类:同步辅助类,是一个统计是否线程结束计数器。 latch:插销 Down:向下
* -countDown() 当前线程调用此方法,则计数器减一
* -await() 调用此方法会一直阻塞线程,直至计数器为0
* */
final CountDownLatch count=new CountDownLatch(threadNum); for (int i = 0; i < threadNum; i++) {//创建5个独立线程,让每个线程去执行调用100次单例模式的getInstance()方法
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 10000; j++) {
//Object object=Singleton04.getInstance();
Object object2=Singleton05.INSTANCE;
}
count.countDown();//执行完一个线程 ,调用countDown()方法自动将threadNum(正在执行的线程总数)减一
}
}).start();
}
count.await();//阻塞main线程,一直等待,循环检测,看其他线程是否执行完才会继续往下 long end=System.currentTimeMillis();
System.out.println("总耗时:"+(end-start)+"毫秒");
}
}

[19/04/22-星期一] GOF23_创建型模式(单例模式)的更多相关文章

  1. 设计模式01 创建型模式 - 单例模式(Singleton Pattern)

    参考 [1] 设计模式之:创建型设计模式(6种) | 博客园 [2] 单例模式的八种写法比较 | 博客园 单例模式(Singleton  Pattern) 确保一个类有且仅有一个实例,并且为客户提供一 ...

  2. [19/04/24-星期三] GOF23_创建型模式(建造者模式、原型模式)

    一.建造者模式 本质:分离了对象子组件的单独构造(由Builder负责)和装配的分离(由Director负责),从而可以构建出复杂的对象,这个模式适用于:某个对象的构建过程十分复杂 好处:由于构建和装 ...

  3. [19/04/23-星期二] GOF23_创建型模式(工厂模式、抽象工厂模式)

    一.工厂模式(分为:简单工厂模式.工厂方法模式.抽象工厂模式) 实现了创建者和调用者的分离 核心本质:1.实例化对象,用工厂方法代替new操作:2.将选择实现类.创建对象统一管理和控制,从而将调用者跟 ...

  4. [19/04/28-星期日] GOF23_结构型模式(享元模式)

    一.享元模式(FlyWeight,轻量级) [共享类与非共享类] /*** *FlyweightFactory享元工厂类: 创建并管理享元对象,享元池一般设计成键值对 */ package cn.sx ...

  5. java架构之路-(设计模式)五种创建型模式之单例模式

    设计模式自身一直不是很了解,但其实我们时刻都在使用这些设计模式的,java有23种设计模式和6大原则. 设计模式是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可 ...

  6. 设计模式04: Factory Methord 工厂方法模式(创建型模式)

    Factory Methord 工厂方法模式(创建型模式) 从耦合关系谈起耦合关系直接决定着软件面对变化时的行为 -模块与模块之间的紧耦合使得软件面对变化时,相关的模块都要随之变更 -模块与模块之间的 ...

  7. 设计模式(3)-对象创建型模式-Abstract Factory模式

    1.对象创建型模式 1.3           Abstract Factory模式 1.3.1 需求 在下面情况能够使用Abstract Factory模式: •  一个系统要独立于它的产品的创建. ...

  8. Java设计模式之五大创建型模式(附实例和详解)

    一.概况 总体来说设计模式分为三大类: (1)创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. (2)结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥 ...

  9. (转)Java经典设计模式(1):五大创建型模式(附实例和详解)

    原文出处: 小宝鸽 一.概况 总体来说设计模式分为三大类: (1)创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. (2)结构型模式,共七种:适配器模式.装饰器模式.代 ...

随机推荐

  1. CSS浮动的3个特性(高手绕行)

    1.  浮动元素会脱离正常的文档流,按照其外边距指定的位置相对于它的上一个块级元素(或父元素)显示: 代码示例: <!DOCTYPE HTML > <html> <hea ...

  2. 三分钟理解Java中字符串(String)的存储和赋值原理

    可能很多Java的初学者对String的存储和赋值有迷惑,以下是一个很简单的测试用例,你只需要花几分钟时间便可理解. 1.在看例子之前,确保你理解以下几个术语: 栈:由JVM分配区域,用于保存线程执行 ...

  3. 1、类、封装(私有private、this关键字)

    类与对象 对象在需求中的使用 ​ 对面向对象有了了解之后,我们来说说在具体问题中如何使用面向对象去分析问题,和如何使用面向对象. ​ 我们把大象装冰箱为例进行分析. ​ 在针对具体的需求,可以使用名词 ...

  4. XML修改节点值

    基于DOM4J 先获取根节点 doc.getRootElement() 然后获取需要修改的节点 doc.getRootElement().node(int) 重新赋值 doc.getRootEleme ...

  5. 基于netcore对ElasitSearch客户端NEST查询功能的简单封装NEST.Repository

    NEST.Repository A simple encapsulation with NEST client for search data form elasticsearch. github A ...

  6. JQuery实现获取多个input输入框的值,并存放在一个数组中

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. react打包开发文件的步骤(上传给线上环境)

    cd进入ReleaseProject目录,然后运行npm start,系统会自动在public目录下面完成打包工作,然后我再把  public文件下压缩位public.rar上传即可:(public文 ...

  8. axios中设置post请求,后台却无法识别参数

    场景:在使用iview时,定义api请求时,代码如下 export const delWord = (data) => { return axios.request({ url: '/words ...

  9. IhyerDB modBus采集器配置.

    近期查了一下ihyerDB-modbus采集器的相关配置,由于没有相关的modbus设备,于是今天上午根据网上的线索下载了Modbus Slave(modbus从站仿真器).笔记本也没有串口,于是下载 ...

  10. Android学习笔记(3)----手机调试[OFFLINE]的解决方式

    问题描述 今天用Android Studio开发了一个简单的调用摄像头的App,结果想调试的时候发现选择调试设备的对话框中,手机名称后面总是跟着一个[OFFLINE]的标识,只能选择启动AVD来进行调 ...