写在前面的话:读书破万卷,编码如有神
--------------------------------------------------------------------
主要内容包括:

  1. 初始单例模式,包括:定义、结构、参考实现
  2. 体会单例模式,场景问题、不用模式的解决方案、使用模式的解决方案
  3. 理解单例模式,包括:认识单例模式、懒汉式和饿汉式实现、延迟加载的思想、缓存的思想、Java中缓存的基本实现、利用缓存来实现单例模式、单例模式的优缺点、在Java中一种更好的单例实现方式、单例和枚举
  4. 思考单例模式,包括:单例模式的本质、何时选用

参考内容:

1、《研磨设计模式》 一书,作者:陈臣、王斌

---------------------------------------------------------------------

1、初识单例模式

1.1、定义

  保证一个类仅有一个实例,并提供一个访问它的全局访问点。

1.2、结构和说明

Singleton: 负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。

1.3、示例代码

在Java中,单例模式的实现又分为两种:懒汉式、饿汉式,下面分别来看看这两种的实现代码:

(1)懒汉式示例代码如下:

 1 /**
 2  * 懒汉式单例实现的示例代码
 3  */
 4 public class Singleton {
 5     //定义一个变量来存储定义好的类实例
 6     private static Singleton instance = null;
 7
 8     /**
 9      * 私有化构造方法,可以在内部控制创建实例的个数
10      */
11     private Singleton(){
12
13     }
14
15     /**
16      * 定义一个方法来为客户端提供类实例
17      * @return 一个Singleton的实例
18      */
19     public static synchronized Singleton getInstance(){
20         //判断存储实例的变量是否有值
21         if(instance == null){
22             //如果没有,就创建一个类实例,并把值赋值给存储类实例的变量
23             instance = new Singleton();
24         }
25         return instance;
26     }
27
28     /**
29      * 单例模式也可以有自己的操作
30      */
31     public void Operation(){
32
33     }
34
35     //单例模式也可以有自己的属性
36     private String data;
37
38     public String getData() {
39         return data;
40     }
41
42     public void setData(String data) {
43         this.data = data;
44     }
45 }

View Code

(2)饿汉式示例代码如下:

 1 /**
 2  * 饿汉式单例实现的示例代码
 3  */
 4 public class Singleton {
 5     //定义一个变量来存储定义好的类实例
 6     private static Singleton instance = new Singleton();
 7
 8     /**
 9      * 私有化构造方法,可以在内部控制创建实例的个数
10      */
11     private Singleton(){
12
13     }
14
15     /**
16      * 定义一个方法来为客户端提供类实例
17      * @return 一个Singleton的实例
18      */
19     public static synchronized Singleton getInstance(){
20         return instance;
21     }
22
23     /**
24      * 单例模式也可以有自己的操作
25      */
26     public void Operation(){
27
28     }
29
30     //单例模式也可以有自己的属性
31     private String data;
32
33     public String getData() {
34         return data;
35     }
36
37     public void setData(String data) {
38         this.data = data;
39     }
40 }

View Code

---------------------------------------------------------------------

2、体会单例模式

2.1、场景问题

  考虑这样一个应用,读取配置文件的内容,在实际的项目中配置文件多采用xml格式或者properties格式的,现在要读取配置文件的内容,该如何实现呢?

2.2、不用模式的解决方案

假设系统采用的是properties格式的配置文件,读取配置文件的示例代码如下:

 1 (1)jdbc.properties文件的内容
 2     jdbcUser=xixixixixi
 3     jdbcPassword=123456
 4
 5 (2)读取配置文件应用程序
 6 import java.io.IOException;
 7 import java.io.InputStream;
 8 import java.util.Properties;
 9
10 /**
11  * 读取应用配置文件
12  */
13 public class AppConfig {
14     //数据库用户名
15     private String jdbcUser;
16     //数据库密码
17     private String jdbcPassword;
18
19     /**
20      * 构造函数
21      */
22     public AppConfig(){
23         //调用读取配置文件的方法
24         readConfig();
25     }
26
27     /**
28      * 读取配置文件,把配置文件中的内容读取出来设置到属性上
29      */
30     private void readConfig(){
31         Properties p = new Properties();
32         InputStream in = null;
33
34         try{
35             in = AppConfig.class.getResourceAsStream("jdbc.properties");
36             p.load(in);
37
38             //把配置文件中的内容读取出来设置到属性上
39             jdbcUser = p.getProperty("jdbcUser");
40             jdbcPassword = p.getProperty("jdbcPassword");
41         }catch(IOException e){
42             e.printStackTrace();
43         }finally{
44             try {
45                 in.close();
46             } catch (IOException e) {
47                 e.printStackTrace();
48             }
49         }
50     }
51
52     public String getJdbcUser() {
53         return jdbcUser;
54     }
55
56     public String getJdbcPassword() {
57         return jdbcPassword;
58     }
59 }
60
61 (3)测试客户端
62 public class Client {
63     public static void main(String[] args) {
64         AppConfig appConfig = new AppConfig();
65
66         String jdbcUser = appConfig.getJdbcUser();
67         String jdbcPassword = appConfig.getJdbcPassword();
68
69         System.out.println("jdbcUser = " + jdbcUser);
70         System.out.println("jdbcPassword = " + jdbcPassword);
71     }
72 }
73
74 (4)运行结果
75 jdbcUser = xixixixixi
76 jdbcPassword = 123456

View Code

2.3、有何问题

上面的实现很简单,很容易就实现要求的功能了。但是来仔细想想,有没有什么问题?

存在的问题:在客户端中是通过new一个AppConfig的实例来得到一个操作配置文件内容的对象,如果在系统运行中,有很多地方都需要使用配置文件的内容,也就是说很多地方都需要创建AppConfig对象的实例。----->换句话说,在系统运行期间,系统中会存在很多个AppConfig的实例对象,每一个AppConfig实例对象里面都封装这配置文件的内容,系统中有多个AppConfig实例对象,也就是说系统中会同时存在多份配置文件的内容,这样会严重浪费内存资源。

把问题抽象一下:在一个系统运行期间,某个类只需要一个类实例就可以了,那么该怎么实现呢?

2.4、使用单例模式来解决问题

(1)解决问题的思路

  现在AppConfig类能被创建多个实例,问题的根源在于类的构造方法是公开的,也就是可以让类的外部来通过构造方法创建多个实例。换句话说,只要类的构造方法能让类的外部访问,就没有办法去控制外部来创建这个类的实例个数。

  要想控制一个类只被创建一个实例,那么首要的问题就是要把创建实例的权限收回来,让类自身来负责自己类实例的创建工作,然后由这个类来提供外部可以访问这个类实例的方法,这就是单例模式的实现方式。

(2)使用单例模式重写示例(饿汉式)

 1 (1)properties属性文件
 2 jdbcUser=xixixixixi
 3 jdbcPassword=123123
 4
 5 (2)采用单例模式实现读取配置文件程序
 6 import java.io.IOException;
 7 import java.io.InputStream;
 8 import java.util.Properties;
 9
10 /**
11  * 读取应用配置文件,单例模式实现
12  */
13 public class AppConfig {
14     //数据库用户名
15     private String jdbcUser;
16     //数据库密码
17     private String jdbcPassword;
18
19     //定义一个变量来存储创建好的类实例
20     private static AppConfig instance = new AppConfig();
21
22     /**
23      * 私有化构造函数
24      */
25     private AppConfig(){
26         //调用读取配置文件的方法
27         readConfig();
28     }
29
30     /**
31      * 定义一个方法来为客户端提供AppConfig类的实例
32      * @return AppConfig类的实例
33      */
34     public static AppConfig getInstance(){
35         return instance;
36     }
37
38     /**
39      * 读取配置文件,把配置文件中的内容读取出来设置到属性上
40      */
41     private void readConfig(){
42         Properties p = new Properties();
43         InputStream in = null;
44
45         try{
46             in = AppConfig.class.getResourceAsStream("jdbc.properties");
47             p.load(in);
48
49             //把配置文件中的内容读取出来设置到属性上
50             jdbcUser = p.getProperty("jdbcUser");
51             jdbcPassword = p.getProperty("jdbcPassword");
52         }catch(IOException e){
53             e.printStackTrace();
54         }finally{
55             try {
56                 in.close();
57             } catch (IOException e) {
58                 e.printStackTrace();
59             }
60         }
61     }
62
63     public String getJdbcUser() {
64         return jdbcUser;
65     }
66
67     public String getJdbcPassword() {
68         return jdbcPassword;
69     }
70 }
71
72 (3)客户端
73 public class Client {
74     public static void main(String[] args) {
75
76         //获取读取应用配置文件的对象
77         AppConfig appConfig = AppConfig.getInstance();
78
79         String jdbcUser = appConfig.getJdbcUser();
80         String jdbcPassword = appConfig.getJdbcPassword();
81
82         System.out.println("jdbcUser = " + jdbcUser);
83         System.out.println("jdbcPassword = " + jdbcPassword);
84     }
85 }
86
87 (4)运行结果
88 jdbcUser = xixixixixi
89 jdbcPassword = 123123

View Code

---------------------------------------------------------------------

3、理解单例模式

3.1、认识单例模式

(1)单例模式的功能

  单例模式是用来保证这个类在运行期间只会被创建一个类实例,并且还提供了一个全局唯一访问这个类实例的访问点。

(2)单例模式的范围

  目前Java里面实例的单例是一个虚拟机的范围,因为装载类的功能是虚拟机的。

3.2、延迟加载的思想

  单例模式的懒汉式实现方式体现了延迟加载的思想,什么是延迟加载呢? 通俗点说,延迟加载就是一开始不要加载资源或者数据,等到要使用的时候再去加载,所以也称为 Lazy Load,这在实际开发中是一种很常见的思想,尽可能地节约资源。

3.3、缓存的思想

  单例模式的懒汉式实现还体现了缓存的思想,简单讲就是,某些资源或者数据被频繁地使用,而这些资源或数据存在在系统外部,比如数据库、磁盘文件等,那么每次操作这些数据的时候都得从数据库或者硬盘上去获取,速度会很慢,将造成性能问题。

  解决办法就是利用缓存: 把这些数据缓存到内存里面,每次操作的时候,先到内存里面找,看有没有这些数据,如果有,就直接使用;如果没有,就去获取它并设置到缓存中,下一次访问的时候就可以直接从内存中获取了,从而节省大量的时间。

(ps:缓存是一种典型的空间换时间的方案)

3.4、Java中缓存的基本实现

在实际开发中常用的缓存方法有很多,比如:memcached、ehcache等。

在Java开发中最常见的一种实现缓存的方式就是使用Map,基本步骤如下:

  1. 先到缓存里面查找,看看是否存在需要使用的数据
  2. 如果没有找到,那么就创建一个满足要求的数据,然后把这个数据设置到缓存中,以备下次使用。
  3. 如果找到了相应的数据,或者创建了相应的数据,那么久直接使用这个数据。

示例代码如下:

 1 import java.util.HashMap;
 2 import java.util.Map;
 3
 4 /**
 5  * Java中缓存的基本实现示例
 6  */
 7 public class JavaCache {
 8     /**
 9      * 缓存数据的容器
10      */
11     private Map<String,Object> map = new HashMap<String,Object>();
12
13     /**
14      * 从缓存中获取值
15      * @param key 设置时候的key值
16      * @return key对应的value值
17      */
18     public Object getValue(String key){
19         //先从缓存中去获取
20         Object obj = map.get(key);
21
22         //判断缓存里面是否有值
23         if(obj == null){
24             //如果没有,那么就去获取相应的数据,比如读取数据库或者文件
25             //这里只是演示,所以直接写个假值
26             obj = key + ",value";
27
28             //把获取的值设置回到缓存里面
29             map.put(key, obj);
30         }
31
32         //如果有值了,就直接使用
33         return obj;
34     }
35 }

View Code

3.5、单例模式的优缺点

(1)时间和空间

  • 懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。
  • 饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。

(2)线程安全

  • 不加同步的懒汉式是线程不安全的,如果有两个线程同时调用getInstance方法,那么可能会导致并发问题。
  • 饿汉式是线程安全的,因此虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。

(3)如何实现懒汉式的线程安全呢?

只要加上synchronized即可,如下:

1 public static synchronized Singleton getInstance()

但是这在方法上加同步控制,会降低整个访问速度,效率比较低。

(4)双重检查加锁

所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查;进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

示例代码如下:

 1 import java.io.IOException;
 2 import java.io.InputStream;
 3 import java.util.Properties;
 4
 5 /**
 6  * 读取应用配置文件,单例模式实现(双重检查方式)
 7  */
 8 public class AppConfig {
 9     //数据库用户名
10     private String jdbcUser;
11     //数据库密码
12     private String jdbcPassword;
13
14     //定义一个变量来存储创建好的类实例
15     private volatile static AppConfig instance = null;
16
17     /**
18      * 私有化构造函数
19      */
20     private AppConfig(){
21         //调用读取配置文件的方法
22         readConfig();
23     }
24
25     /**
26      * 定义一个方法来为客户端提供AppConfig类的实例
27      * @return AppConfig类的实例
28      */
29     public static AppConfig getInstance(){
30         //先检查实例是否存在,如果不存在则进入下面的同步块
31         if(instance == null){
32             //同步块,线程安全的创建实例
33             synchronized(AppConfig.class){
34                 //再次检查实例是否存在,如果不存在才真正的创建实例
35                 if(instance == null){
36                     instance = new AppConfig();
37                 }
38             }
39         }
40
41         return instance;
42     }
43
44     /**
45      * 读取配置文件,把配置文件中的内容读取出来设置到属性上
46      */
47     private void readConfig(){
48         Properties p = new Properties();
49         InputStream in = null;
50
51         try{
52             in = AppConfig.class.getResourceAsStream("jdbc.properties");
53             p.load(in);
54
55             //把配置文件中的内容读取出来设置到属性上
56             jdbcUser = p.getProperty("jdbcUser");
57             jdbcPassword = p.getProperty("jdbcPassword");
58         }catch(IOException e){
59             e.printStackTrace();
60         }finally{
61             try {
62                 in.close();
63             } catch (IOException e) {
64                 e.printStackTrace();
65             }
66         }
67     }
68
69     public String getJdbcUser() {
70         return jdbcUser;
71     }
72
73     public String getJdbcPassword() {
74         return jdbcPassword;
75     }
76 }

View Code

双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存的,从而确保多个线程能正确的处理该变量。

3.6、在Java中一种更好的单例实现方式

下面这种方式既能够实现延迟加载,又能够实现线程安全(采用类级内部类的方式)。

示例代码:

 1 public class Singleton {
 2
 3     /**
 4      * 私有化构造方法
 5      */
 6     private Singleton(){
 7
 8     }
 9
10     /**
11      * 类级的内部类,也就是静态的成员内部类,该内部类的实例与外部类的实例没有绑定关系,
12      * 而且只有被调用到时才会装载,从而实现延迟加载
13      */
14     private static class SingletonHolder{
15         /**
16          * 静态初始化器,由JVM来保证线程安全
17          */
18         private static Singleton instance = new Singleton();
19     }
20
21     public static Singleton getInstance(){
22         return SingletonHolder.instance;
23     }
24 }

上面的代码使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙地同时实现了延迟加载和线程安全。

相关的基础知识:

(1)类级内部类

  • 什么是类级内部类? 简单点说,类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。
  • 类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
  • 类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
  • 类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。

(2)多线程缺省同步锁的知识

在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况下,JVM已经隐含地为我们执行了同步,这些情况下就不用自己再来进行同步控制了,这些情况包括:

  • 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
  • 访问final字段时
  • 在创建线程之前创建对象时
  • 线程可以看见它将要处理的对象时

3.7、单例和枚举

3.8、思考单例模式

(1)单例模式的本质: 控制实例数目。

(2)何时选用单例模式

  当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式。

---------------------------------------------------------------------

---------------------------------------------------------------------

--------------------------------------------------------------------- 

转载于:https://www.cnblogs.com/xinhuaxuan/p/6424926.html

设计模式 -- 单例模式(Singleton)相关推荐

  1. Android设计模式——单例模式(Singleton)

    二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元 ...

  2. 常见设计模式—单例模式(Singleton)

    前言 好久没写东西了,但是想着无论什么事还是要坚持自己初心要坚持的东西.写东西不能断! 对于常用的23种设计模式,这里笔者会根据自己学习和出现频率.重要程度进行学习记录吧.并且每种设计模式可能会根据暂 ...

  3. C++设计模式--单例模式(Singleton)及单例通用模板

    概述 C++中的单例模式应该是设计模式中最简单的了,在编码中常见到.那么,简单的总结下 C++中的单例模式写法,以及根据单例模式扩展后的一些写法,最后还有单例的通用模板,可用于快捷创建一个单例类. 单 ...

  4. 设计模式——单例模式(Singleton)

    保证一个类仅有一个实例,并提供一个访问它的全局访问点.--DP UML类图 模式说明 个人认为单例模式是所有设计模式中最为简单的一个模式,因为实现这个模式仅需一个类,而不像其他模式需要若干个类.这个模 ...

  5. Python 设计模式: 单例模式(singleton pattern)

    2019独角兽企业重金招聘Python工程师标准>>> 如果想在整个程序的运行过程中,某个类只有一个实例的话,可以通过单例模式来实现. 在 Python 中实现单例模式有很多种方式, ...

  6. 解读设计模式----单例模式(Singleton Pattern)

    单例模式可以保证一个类有且只有一个实例,并提供一个访问它的全局访问点.在程序设计中,有很多情况需要确保一个类只能有一个实例.从这句话可以看出,Singleton模式的核心:如何控制用户使用new对一个 ...

  7. JAVA设计模式-单例模式(Singleton)线程安全与效率

    一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式 ...

  8. 设计模式---单例模式Singleton

    /*** 饿汉式* 类加载到内容后,就实例化一个实例,* JVM保证线程安全: JVM保证每一个Class只会load到内存一次,static关键字是在Class load到内存之后马上就行初始化,也 ...

  9. 趣谈设计模式 | 单例模式(Singleton) :独一无二的对象

    文章目录 单例模式 饿汉模式 懒汉模式 懒汉和饿汉的区别 单例模式 一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全 局访问点,该实例被所有程序模块共享. ...

最新文章

  1. 第十七章 大规模机器学习-机器学习老师板书-斯坦福吴恩达教授
  2. Mac 配置支持 opengl 的 opencv 4.2
  3. Spring的WebClient基本使用
  4. 【C++grammar】继承与构造
  5. 省份城市区县三级联动html代码,基于Jquery实现省份、城市、区县三级联动
  6. 微软IE浏览器1月市场份额再创新低 跌至67.6%
  7. 配置JDK环境变量详细步骤
  8. 指令级并行——超标量Superscalar与超长指令字VLIW架构
  9. 《财务报表分析从入门到精通》——读书笔记
  10. 公务员考试——申论2
  11. Adobe Photoshop 7.0.1 简体中文版注册码
  12. 安全架构--13--企业资产管理体系建设总结
  13. 推荐两个Android模拟器,轻松解决模拟器调试java.lang.UnsatisfiedLinkError: dlopen failed~~
  14. 如果我们总是等待绝对的一切就绪,那我们将永远无法开始
  15. 02_星仔带你学Java之变量、常量、字面量、数据类型
  16. keras深度学习安装全过程(2021-08-03)
  17. Java实现多重继承
  18. 实战项目:餐厅订单数据分析:订单维度和时间维度
  19. spring spel 获取环境变量
  20. [4G5G专题-31]:物理层-物理信道、信号、映射概述

热门文章

  1. windows的cmd与bat批处理脚本(batch script)
  2. [1057]VMware安装的虚拟机窗口如何自适应屏幕大小
  3. ThinkPad E425 1198A19 在win7下安装Ubuntu双系统卡在安装界面的问题
  4. 产品分型面、插靠破、潜水进胶注意事项!
  5. 单层神经网络实现手写数字识别
  6. 安卓Android与H5双向交互MathJax展示数学公式(源码+解析)
  7. 该怎么学好软件工程这门课?
  8. 关于logcat的使用
  9. 【数据库学习】非关系数据库(NoSQL:“non-relational”)
  10. 安卓设备互相投屏_安卓投屏电脑版-安卓投屏下载v7.9.1-IE浏览器中文网站