java设计模式学习笔记,一:单例模式

     2022-03-20     564

关键词:

  开始学习Java的设计模式,因为做了很多年C语言,所以语言基础的学习很快,但是面向过程向面向对象的编程思想的转变还是需要耗费很多的代码量的。所有希望通过设计模式的学习,能更深入的学习。

  把学习过程中的笔记,记录下来,只记干货。

 

第一部分:单例模式的内容

  单例模式:类只能有一个实例。

  类的特点:1、私有构造器;2、内部构造实例对象;3、对外提供获取唯一实例的public方法。

  常见的单例模式实现有五种形式:

    1、饿汉式。

    2、懒汉式。

    3、双重检查锁式。

    4、静态内部类式。

    5、枚举式。

  以下分别介绍:

  一、饿汉式

    饿汉式单例特点:线程安全,不能延时加载,效率较高。

 1 public class SingletonDemoE {
 2     
 3     //内部构建唯一实例
 4     private static SingletonDemoE instance = new SingletonDemoE();
 5     
 6     //私有化构造器
 7     private SingletonDemoE(){
 8         
 9     }
10     
11     //公共静态方法获取唯一实例化对象
12     public static SingletonDemoE getInstance(){
13         return instance;
14     }
15     
16 }

  二、懒汉式

    懒汉式单例特点:线程安全(须synchronized做方法同步),可以延时加载,效率较低。

 1 public class SingletonDemoL {
 2     
 3     //声明实例对象
 4     private static SingletonDemoL instance;
 5     
 6     //私有化构造器
 7     private SingletonDemoL(){
 8         
 9     }
10     
11     //公共静态方法获取唯一实例化对象,方法同步
12     public static synchronized SingletonDemoL getInstance(){
13         if(instance == null){
14             //第一次实例化时构建
15             instance = new SingletonDemoL();
16         }
17         return instance;
18     }
19     
20 }    

 

  三、双重检查锁式

    结合了饿汉式和懒汉式的优点,但由于JVM底层内部模型原因,偶尔会出问题,所以不建议使用,本文不赘语。

  四、静态内部类式

    静态内部类式单例特点:线程安全(须synchronized做方法同步),可以延时加载,效率较高。

 1 public class SingletonDemoJ {
 2     
 3     //静态内部类
 4     private static class SingletonClassInstance {
 5         private static final SingletonDemoJ instance = new SingletonDemoJ();
 6     }
 7     
 8     //私有化构造器
 9     private SingletonDemoJ(){
10         
11     }
12     
13     //公共静态方法获取唯一实例化对象,方法同步
14     public static synchronized SingletonDemoJ getInstance(){
15         return SingletonClassInstance.instance;
16     }
17     
18 }

 

  五、枚举式

    枚举式单例特点:枚举是天然的单例,线程安全,不可延时加载,效率较高

1 public enum SingletonDemoM {
2     //枚举元素,本身就是单例模式
3     INSTANCE;
4     
5     //实现自己的操作
6     public void singletonOperation(){
7         
8     }
9 }

 

第二部分:单例模式的破解(扩展)

  单例模式的五种实现方式中,除枚举式是天然的单例不可破解之外,其他四种形式均可通过反射和反序列化的机制进行破解。

  以懒汉式单例为例,首先分别看一下如何通过反射和反序列化的机制破解单例。

  定义一个常规的懒汉式单例:

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

  正常我们创建多个单例的实例,都应该是同一个对象,如下测试Demo:

1 public class TestCrackDemo {
2     public static void main(String[] args) {
3         SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance();
4         SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance();
5         System.out.println("sL1 = " + sL1);
6         System.out.println("sL2 = " + sL2);
7     }
8 }

  运行返回:

  利用反射机制破解单例,创建多个不同的实例:

 1 package com.corey.singleton;
 2 
 3 import java.lang.reflect.Constructor;
 4 
 5 public class TestCrackDemo {
 6     public static void main(String[] args) {
 7         SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance();
 8         SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance();
 9         System.out.println("sL1 = " + sL1);
10         System.out.println("sL2 = " + sL2);
11         
12         //利用反射机制破解单例
13         try {
14             Class<SingletonDemoAntiCrackL> clazz = (Class<SingletonDemoAntiCrackL>)Class.forName("com.corey.singleton.SingletonDemoAntiCrackL");
15             Constructor<SingletonDemoAntiCrackL> c = clazz.getDeclaredConstructor(null);
16             c.setAccessible(true); //跳过权限检查,可以访问私有属性和方法
17             SingletonDemoAntiCrackL sL3 = c.newInstance(null);    //构建实例
18             SingletonDemoAntiCrackL sL4 = c.newInstance(null);
19             System.out.println("sL3 = " + sL3);
20             System.out.println("sL4 = " + sL4);
21             
22         } catch (Exception e) {
23             e.printStackTrace();
24         }
25     }
26 }

  运行返回:

  可见,通过反射构建的sL3和sL4两个对象,都是不同的实例,破解了单例模式只能有一个实例的要求。

  那么,修改单例的构造函数,可以应对反射机制的破解,代码如下:

 1 public class SingletonDemoAntiCrackL {
 2     private static SingletonDemoAntiCrackL instance;
 3     
 4     private SingletonDemoAntiCrackL(){
 5         //私有构造器,增加实例检查,若已创建实例,则抛出异常
 6         if(instance != null){            
 7             throw new RuntimeException();
 8         }
 9     }
10 
11     public static synchronized SingletonDemoAntiCrackL getInstance(){
12         if(instance == null){
13             instance = new SingletonDemoAntiCrackL();
14         }
15         return instance;
16     }
17 }

  此时,在运行TestCrackDemo时,会抛出java.lang.reflect.InvocationTargetException异常,避免了通过反射机制创建多个实例的问题。

  接下来,看下通过反序列化机制破解单例。

  当单例的类实现了Serializable接口时,就可以通过反序列化机制破解,如下单例:

 1 public class SingletonDemoAntiCrackL implements Serializable{
 2     private static SingletonDemoAntiCrackL instance;
 3     
 4     private SingletonDemoAntiCrackL(){
 5         //私有构造器,增加实例检查,若已创建实例,则抛出异常
 6         if(instance != null){            
 7             throw new RuntimeException();
 8         }
 9     }
10 
11     public static synchronized SingletonDemoAntiCrackL getInstance(){
12         if(instance == null){
13             instance = new SingletonDemoAntiCrackL();
14         }
15         return instance;
16     }
17 }

  反序列化破解测试Demo:

 1 package com.corey.singleton;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.ObjectInputStream;
 7 import java.io.ObjectOutputStream;
 8 import java.lang.reflect.Constructor;
 9 
10 public class TestCrackDemo {
11     public static void main(String[] args) {
12         SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance();
13         SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance();
14         System.out.println("sL1 = " + sL1);
15         System.out.println("sL2 = " + sL2);
16 
17         //通过反序列化机制破解单例
18         try {
19             //序列化,将对象存入文件
20             FileOutputStream fos = new FileOutputStream("f:/fos.txt");
21             ObjectOutputStream oos = new ObjectOutputStream(fos);
22             oos.writeObject(sL1);
23             oos.close();
24             fos.close();
25             //反序列化,从文件中读出对象
26             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("f:/fos.txt"));
27             SingletonDemoAntiCrackL sL5 = (SingletonDemoAntiCrackL)ois.readObject();
28             System.out.println("sL5 = " + sL5);
29         } catch (Exception e) {
30             e.printStackTrace();
31         }
32     }
33 }

  运行的结果:

  可见,反序列化出来的实例对象,是不同的对象,即单例已被破解。

  解决版本,单例类中重写readResolve方法,可以应对反射机制的破解,代码如下:

 1 package com.corey.singleton;
 2 
 3 import java.io.ObjectStreamException;
 4 import java.io.Serializable;
 5 
 6 /**
 7  * 单例模式,懒汉式
 8  * 线程安全,能延时加载,效率相对较低
 9  * @author Corey
10  *
11  */
12 public class SingletonDemoAntiCrackL implements Serializable{
13     private static SingletonDemoAntiCrackL instance;
14     
15     private SingletonDemoAntiCrackL(){
16         //私有构造器,增加实例检查,若已创建实例,则抛出异常
17         if(instance != null){            
18             throw new RuntimeException();
19         }
20     }
21 
22     public static synchronized SingletonDemoAntiCrackL getInstance(){
23         if(instance == null){
24             instance = new SingletonDemoAntiCrackL();
25         }
26         return instance;
27     }
28     
29     private Object readResolve() throws ObjectStreamException{
30         //反序列化时,直接返回对象
31         return instance;
32     }
33 }

  修改后,再次运行TestCrackDemo,可以看到反序列化后,构建的仍然是同一个对象。

 

第三部分:单例模式各个实现方式的效率

  采用如下代码,测试:

 1 package com.corey.singleton;
 2 
 3 import java.util.concurrent.CountDownLatch;
 4 
 5 /**
 6  * 测试单例模式的效率
 7  * @author Corey
 8  *
 9  */
10 public class TestEfficiencyDemo {
11     public static void main(String[] args) throws Exception {
12         
13         int threadNum = 10;
14         long start = System.currentTimeMillis();
15         
16         final CountDownLatch cdl = new CountDownLatch(threadNum);
17         
18         //创建10个线程
19         for(int k=0; k<threadNum; k++){
20             new Thread(new Runnable() {
21                 
22                 @Override
23                 public void run() {
24                     //每个线程构建100万个实例对象
25                     for(int i=0; i<1000000; i++){
26                         Object o = SingletonDemoE.getInstance();
27                     }
28                     //每个线程运行完毕,线程计数器减一
29                     cdl.countDown();
30                 }
31             }).start();
32         }
33         
34         cdl.await();//main线程阻塞,直到现场计数器为0,才继续执行。
35         
36         long end = System.currentTimeMillis();
37         System.out.println("饿汉式总耗时:" + (end - start));
38     }
39 }

  运行结果:

饿汉式总耗时:17

  以此类推,测试各个实现方式的单例的效率。注意,此处根据电脑性能以及电脑的运行情况不同,结果都是不一样的,甚至同一实现方式,多次运行的结果也不一样。

  我这里的测试结果如下:

饿汉式总耗时:17
懒汉式总耗时:171
静态内部类式总耗时:165
枚举式总耗时:11

 

  以上就是设计模式中的单例模式!

单例模式的学习笔记

java设计模式之单例模式什么叫单例模式(实例有且只有一个)/*单例模式Singleton应用场合:有些对象只需要一个实例就够了作用:保证整个应用程序中某个实例有且只有一个类型:懒汉模式饿汉模式*/饿汉模式publicclassSingleton{//1... 查看详情

设计模式:学习笔记——单例模式

Java设计模式之单例模式引入单例模式什么是单例模式  对于某些类来说,我们只想其拥有一个实例,并且我们仅仅使用这一个实例:比如说线程池,缓存,对话框,处理偏好设置和注册表的对象、日志对象、显卡等设备的驱... 查看详情

java:java学习笔记之java单例模式的简单理解和使用(代码片段)

Java单例模式的简单理解和使用Java单例模式1、饿汉式单例的实现如下:2、懒汉式终极版本:volatile3、EffectiveJava1——静态内部类4、5.2EffectiveJava2——枚举参考Java单例模式1、饿汉式单例的实现如下://饿汉式实现publiccl... 查看详情

设计模式学习笔记-单例模式

一、概述   保证一个类仅有一个实例,并提供一个访问它的全局访问点。二、模式中的角色   Singleton:定义一个Instance操作,允许客户访问它的唯一实例,Instance是一个类操作;可能负责创建它自己的唯一... 查看详情

设计模式学习笔记之单例模式

...、饿汉式。2.观点:严格来说,单例模式并不应该算得上设计模式,纠结线程安全问题可以,但是纠结茴香豆的八种写法这 查看详情

《java与模式》学习笔记——singleton

单例(Singleton)模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。一些资源管理器常常设计成单例模式。 单例模式的结构如下:... 查看详情

学习笔记spring中常用的设计模式(代码片段)

文章目录简介一.Spring用到的设计模式类别1.创建型模式2.结构性模式3.行为型模式二.设计模式详解1.工厂模式1.1简单工厂模式1.2工厂方法模式1.3抽象工厂模式2.单例模式2.1饿汉模式2.2懒汉模式2.3破坏单例的方式2.4注册式单例模式2.4... 查看详情

单例模式学习笔记

1单例模式(SingletonPattern)介绍 1.1单例模式介绍  定义:确保某一类只有一个实例,而且自行实例化并向整个系统提供这个实例。  实现:通过使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化。... 查看详情

《java与模式》学习笔记——multiton

所谓的多例模式(MultitonPattern),实际上就是单例模式的推广。作为对象的创建模式,多例模式或多例类有以下的特点:多例类可有多个实例。多例类必须自己创建、管理自己的实例,并向外界提供自己的... 查看详情

java设计模式学习笔记

设计模式分为3大类型共23种:创建型:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。结构型:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。行为型:策... 查看详情

java——单例模式学习理解(代码片段)

  单例模式,总而言之还是多种多样的,理解不同的单例模式,对于开发过程中更是极为方便,以下汇总几种单例的使用,积累备用。  一、只适合单线程环境packagetest;/***@authorxiaoping**/publicclassSingletonpriva... 查看详情

java单例设计模式

  学习java单例设计模式,看了几篇比较好的文章和部分书籍资料,在此做一个总结供交流学习一、单例设计模式的作用  单例设计模式可以保证某个类的对象只有一个且所有人使用的是同一个对象(下面会用代码... 查看详情

java笔记之静态修饰附和单例设计模式

                           第六天笔记静态修饰符static:一.static修饰成员变量:static 查看详情

单例模式之个人笔记

概念理解:关于单例模式,顾名思义,就是单一的实例,也就是说,实例只有一个。比如一个公司只有一辆公家车,你出门开这辆车,别人出门也开这辆车,你们每次开的其实是同一辆车,车只有一辆,对应java就是,我调用的... 查看详情

单例设计模式详解+源代码+jdk源码应用——java设计模式系列学习笔记(代码片段)

@toc一.基本介绍采取一定的方法保证在整个的软件系统中,对某个类智能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。比如Hibernate的SessionFactor,他充当数据存储源的代理,并负责... 查看详情

java单例模式

由于经常用笔记本上网,还是在博客里备份一下自己的学习成果比较好,复习起来也很方便。从今天开始把自己会的知识总结在博客里。以下为单例模式里的两种方法的代码:饿汉模式和懒汉模式。/***单例模式Singleton*应用场合... 查看详情

java设计模式——单例设计模式(代码片段)

我们一般在学习的第一个设计模式都是单例设计模式,因为单例设计模式广泛的应用在我们程序开发中。今天就和大家简单的聊一聊单例设计模式,因为在我们刚开始学习Java基础时,就了解了单例,有的同学在学... 查看详情

设计模式笔记-单例模式(代码片段)

设计模式笔记-单例模式文章目录设计模式笔记-单例模式1.单例模式2.常见写法3.常见框架中的单例模式1.单例模式在单例模式中,对单例类的所有实例化得到的都是相同的一个实例2.常见写法1.饿汉式(静态初始化)/***... 查看详情