- 单例模式
【2021版】马士兵重讲23种设计模式,居然能这么通俗易懂_哔哩哔哩_bilibili
Singleton
单例即只有一个实例。
当整个程序中,某个对象只需要有一个唯一的实例存在的时候,就可以使用单例模式。
例如:
在确定某个对象在整个程序只会有唯一的一个实例时,可以在代码中加一些限制,保证他只有一个实例(比如不能被别人new出来第二个实例等),这种编程方式就叫单例模式。
- 名称:单例模式Singleton
- 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 主要解决的问题:一个全局使用的类频繁地创建与销毁。
- 何时使用:当您想控制实例数目,节省系统资源的时候。
- 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 关键代码:构造函数是私有的。(即不允许别人new它)
- 应用实例:
- 一个班级只有一个班主任。
- Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
- 1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
- 2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 4. 应用程序的日志应用,一般都用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。【一些文件锁】
- 5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
- 6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
- 7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
- 8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
- 9. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
- 优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。【比如规定只有一个实例可以操作某一个文件,从另一个角度实现了锁】
- 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
- 使用场景:要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
- 注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。1)
单例的JAVA实现方法(严格来说有8种写法,有2种时完美无缺的,但我们实际工作中写也不一定就要用完美无缺的):
- 饿汉式:
写法1:定义静态变量并自己初始化
步骤:
(1)核心是将构造方法设为private,这样其他地方就没法new它。
(2)唯一的一个实例INSTANCE在自己代码里面new了
(3)要用它的时候,只需要调用它的getInstance获取这个INSTANCE,这个static final变量即可。
优缺点:
(1)在类加载的时候 这个INSTANCE就会实例化 JVM会保证线程安全(JVM保证每一个类只会load到内存一次,而且可以多线程访问这个实例)
(2)简单好用,推荐使用。
(3)但如果没用到 这个INSTANCE还是会实例化 就可能浪费资源
饿汉:即不管你用不用 我先加载了再说(static变量只要加载了就会初始化)
懒汉:什么时候用的时候 我才初始化
写法2:利用静态代码块初始化:
- 懒汉式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的逻辑)
- 懒汉式+synchronized 修饰getInstance方法:加锁
加锁解决了线程不安全的问题 但性能下降
看看synchronized的写法
- 懒汉式+双重检查:
即首先判断INSTANCE是否==null 如果为空 上锁, 上锁后再判断一次INSTANCE是否==null(保证再16-18行时万一有人刚new出一个对象):【有点像CAS 拿数据时记录一下 中间进行数据处理的操作(这里就是拿锁操作) 写数据时比较一下数据是否被别人改过】
【以前单位】
- 最完美的懒汉式写法:静态内部类
单例类的内部定义一个静态内部类,在这个内部类里面定义一个INSTANCE并初始化。然后外部类里面的getIntsance就返回这个内部类里面定义的这个INSTANCE
和第一种方法的区别就是INSTANCE的定义 外面又包了一层类
好处:
1)实现了lazy loading
和第一种方法区别就是 第一种的INSTANCE变量在类加载的时候必须初始化 而这种的这个INSTANCE在类加载的时候不会初始化 只有在第一次调用getInstance的时候才会初始化
【这种写法和第一种写法能体现程序员水平】【这个方法也不难,可以记一下】
2)实现了线程安全:JVM保证每个类只加载一次,所以不需要锁啊啥的
(6)最最完美:《Effective Java写法》枚举单例写法
好处:不仅可以解决线程同步问题,还可以防止反序列化