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

  • 单例模式

【2021版】马士兵重讲23种设计模式,居然能这么通俗易懂_哔哩哔哩_bilibili

单例模式的常见应用场景 - 简书

单例模式 | 菜鸟教程 (runoob.com)

Singleton

单例即只有一个实例。

当整个程序中,某个对象只需要有一个唯一的实例存在的时候,就可以使用单例模式。

例如:

在确定某个对象在整个程序只会有唯一的一个实例时,可以在代码中加一些限制,保证他只有一个实例(比如不能被别人new出来第二个实例等),这种编程方式就叫单例模式。

  1. 名称:单例模式Singleton
  2. 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  3. 主要解决的问题:一个全局使用的类频繁地创建与销毁。
  4. 何时使用:当您想控制实例数目,节省系统资源的时候。
  5. 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  6. 关键代码:构造函数是私有的。(即不允许别人new它)
  7. 应用实例:
    1. 一个班级只有一个班主任。
    2. Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
    3. 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
    4. 1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
    5. 2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
    6. 3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
    7. 4. 应用程序的日志应用,一般都用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。【一些文件锁】
    8. 5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
    9. 6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
    10. 7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
    11. 8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
    12. 9. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
  8. 优点:
    1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
    2. 避免对资源的多重占用(比如写文件操作)。【比如规定只有一个实例可以操作某一个文件,从另一个角度实现了锁】
  9. 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
  10. 使用场景:要求生产唯一序列号。
  11. WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  12. 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
  13. 注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。1)

单例的JAVA实现方法(严格来说有8种写法,有2种时完美无缺的,但我们实际工作中写也不一定就要用完美无缺的):

  1. 饿汉式:

写法1:定义静态变量并自己初始化

步骤:

(1)核心是将构造方法设为private,这样其他地方就没法new它。

(2)唯一的一个实例INSTANCE在自己代码里面new了

(3)要用它的时候,只需要调用它的getInstance获取这个INSTANCE,这个static final变量即可。

优缺点:

(1)在类加载的时候 这个INSTANCE就会实例化 JVM会保证线程安全(JVM保证每一个类只会load到内存一次,而且可以多线程访问这个实例)

(2)简单好用,推荐使用。

(3)但如果没用到 这个INSTANCE还是会实例化 就可能浪费资源

饿汉:即不管你用不用 我先加载了再说(static变量只要加载了就会初始化)

懒汉:什么时候用的时候 我才初始化

写法2:利用静态代码块初始化:

  1. 懒汉式lazy loading:

写法:

核心在于把初始化操作放在getInstance里面,只有别人调了getInstance才初始化【注意现在INSTANCE不能加final 因为加了final就必须初始化】

优缺点:按需初始化,但在多线程访问时存在问题:

即假设T1调用getInstance方法,进入if的then branch,当还没new出来时(比如这里面then branch还执行了一些其他耗时操作 比如上面代码的sleep),这时候另一个线程T2调用getInstance方法,因为T1还没创建出实例,所以T2这里也会判断出INSTANCE==null,也进入then branch,导致最后会创建出来两个实例。

这里面的main就是测试多线程访问的。(用了Java 8 Lambda,省略了new Runnable和重写run 直接写了run的逻辑)

  1. 懒汉式+synchronized 修饰getInstance方法:加锁

加锁解决了线程不安全的问题 但性能下降

看看synchronized的写法  

  1. 懒汉式+双重检查:

即首先判断INSTANCE是否==null 如果为空 上锁, 上锁后再判断一次INSTANCE是否==null(保证再16-18行时万一有人刚new出一个对象):【有点像CAS 拿数据时记录一下 中间进行数据处理的操作(这里就是拿锁操作) 写数据时比较一下数据是否被别人改过】

【以前单位】

  1. 最完美的懒汉式写法:静态内部类

单例类的内部定义一个静态内部类,在这个内部类里面定义一个INSTANCE并初始化。然后外部类里面的getIntsance就返回这个内部类里面定义的这个INSTANCE

和第一种方法的区别就是INSTANCE的定义 外面又包了一层类

好处:

1)实现了lazy loading

和第一种方法区别就是 第一种的INSTANCE变量在类加载的时候必须初始化 而这种的这个INSTANCE在类加载的时候不会初始化 只有在第一次调用getInstance的时候才会初始化

【这种写法和第一种写法能体现程序员水平】【这个方法也不难,可以记一下】

2)实现了线程安全:JVM保证每个类只加载一次,所以不需要锁啊啥的

(6)最最完美:《Effective Java写法》枚举单例写法

好处:不仅可以解决线程同步问题,还可以防止反序列化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值