浅谈设计模式单例模式研究,单例模式5种
单例模式(Singleton)是一种常用的设计模式,它是创建型模式的一种,适用于一个类有且只有一个实例的情况,也就是说,单例模式确保了某个类只有一个实例(对象)存在。
单例模式定义的三个要素定义私有的静态成员。 构造函数私有化。提供一个公有的静态方法以构造实例。单例模式的实现方式对于单例模式,一定要考虑并发状态下的同步问题,单例模式根据实例化对象时间的不同在实现代码时分为两种主流的实现方式,一种叫作饿汉式单例,另一种叫作懒汉式单例,这两种实现方式都是多线程安全的,但前者是天生多线程安全。
饿汉式单例的实现方式:在单例类被加载时,就实例化一个对象。懒汉式单例的实现方式:调用取得实例的方法时才会实例化对象。饿汉式单例模式的Java 实现代码如下:
// 单例模式(饿汉式)public class Singleton { private static Singleton instance = new Singleton(); private Singleton() {}; public static Singleton getInstance() { return instance; }}懒汉式单例模式的Java 实现代码如下:
//单例模式(懒汉式)public class Singleton { private static Singleton instance; private Singleton() {}; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }}在Java中,因为饿汉式实现方案天生线程安全,而懒汉式需要加号synchronized 关键字,影响了性能,所以饿汉式单例性能要优于懒汉式单例。
单例模式的优点避免了频繁地创建和销毁对象,减少了系统开销。节省了内存空间,在内存中只有一个对象。提供了一个全局访问点。单例模式的适用场景针对某些需要频繁创建对象又频繁销毁对象的类。需要经常用到对象,但创建时消耗大量资源。针对确实只能创建一个对象的情况,比如某些核心交易类,只允许保持一个对象。单例模式代码测试编写懒汉式单例模式:
public class Singleton { private static Singleton instance; private Singleton() {}; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }}编写main方法:
public class Test { public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); if (s1 == s2) { System.out.println("s1 和 s2是同一个实例"); }else { System.out.println("s1 和 s2是不同的实例"); } }}运行结果:
由此可以看出,单例模式只会创建一个实例对象。
单例模式对实例的对象做出来限制,为什么要限制呢?因为在一些情况下,当存在多个实例时,实例之间会相互影响,可能产生意想不到的Bug;但是,当我们可以确保只存在一个实例时,在这个前提下程序就可以放心的运行了。
常见问题单例模式两种实现方式的区别?单例模式主要有两种实现方式:饿汉式和懒汉式,两种实现方式是有区别的。
饿汉式是在加载类时就创建了类的一个对象;而懒汉式则是在调用实例方法getInstance()才会创建一个对象。
懒汉式单例模式的劣势?从多线程的角度,懒汉式单例模式是不安全的,所以为了保障线程安全,一般的实现方式是在实例创建的方法getlnstance()前加 synchronized 保障线程安全,即: public static synchronized Singleton getlnstance(){} 。这种实现方案虽然简单,但是缺点也比较明显;这种实现方式降低了整个实例化的性能。
如何改进懒汉式单例模式?不要在getlnstance()方法上进行同步,而是在方法内部进行同步。
具体操作如下:
进入方法后先检查实例是否已经存在,如果不存在则进入同步块;如果存在则直接返回该实例。如果进入了同步块,则再次检查实例是否存在,如果不存在,就在同步块中创建实例;如果存在则直接返回该实例。这种方法因为要经过两次“检查”,所以被称为“双重检查加锁机制”,这种方案既能实现线程安全,又能最大限度地减少性能影响。
双重检查加锁机制的实现代码:
public class Singleton { private volatile static Singleton instance = null; private Singleton() {}; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }}需要注意的是,双重检查加锁机制用到一个关键字 volatile,用volatile 关键字修饰的变量不会被本地线程缓存,即变量的读写直接操作共享内存,这样可以保证多个线程正确使用该变量。
单例模式总结单例模式有两种实现方式:饿汉式(类加载时创建实例) 和 懒汉式(调用实例方法时创建实例)。
单例模式只会创建一个实例,常用于某些核心类。
单例模式中的饿汉式是天生线程安全的,懒汉式需要后天进行改进才会线程安全。需要注意的是懒汉式如何进行性能改进,即“双重检查加锁机制”。
作者:城北有个混子 链接:https://www.cnblogs.com/ruoli-0/p/13810565.html
设计模式 之 单例模式
1. 定义
单例模式指的是一个类,在全局范围内(整个系统中)有且只能有一个实例存在。即该类本身负责提供一种访问其唯一对象的方式,不对外提供公共的构造函数(禁用默认公共构造函数),对于该类的实例化由它自己在类的内部进行维护!
2. 优缺点
- 优点
1. 最大程度的减少了对象的创建和销毁的次数,从而降低的垃圾回收的次数
2. 节约了系统资源,尤其是内存资源
- 缺点
1. 不能继承,不能被外部实例化
2. 类干预了外部类的使用(外部实用类不能随意实例化),而不再仅仅专注于内部的逻辑(与单一职责模式有矛盾)
3. 使用场景
- 有频繁的实例化后又销毁的情况,适合考虑使用单例模式,如记录日志的log对象
- 创建对象需要消耗过多的系统资源,但又经常用到的资源,如数据库连接
4. 框架中的应用
5. 实现方式
单例模式有多种实现方式,要考虑到多线程下的安全性,其每种实现方式如下所示:
以上方式,如果存在多个线程同时访问getInstance()时,由于没有锁机制,会导致实例化出现两个实例的情况,因此,在多线程环境下时不安全的。
如上代码所示,在getInstance()方法上添加了同步锁。但是该方法虽然解决了线程安全的问题,但却也带来了另外的一个问题,就是每次获取对象时,都要先获取锁,并发性能很差,还需要继续优化!
该方法将方法上的锁去掉了,避免了每次调用该方法都要获取锁的操作,从而提升了并发性能,同时在方法内部使用锁,进而解决了并发的问题,从而解决了上面**并发安全+性能低效**的问题,是个不错的实现单例的方式。
该方式虽然简单也安全,但是会造成再不需要实例时,产生垃圾对象,造成资源狼粪,因此,一般不使用。
这种方式可以达到跟** 双重校验锁 **一样的效果,但只适用于静态域的情况,双重校验锁可在实例域需要延迟初始化时使用
这是实现单例模式的最佳方法,更加简洁,自动支持序列化,防止多次实例化,非常高效! (强烈推荐使用)
6.引用
设计模式之单例模式
本文开始整个设计模式的系列学习,希望通过不断的学习,可以对设计模式有整体的掌握,并在项目中根据实际的情况加以利用。单例模式是指一个类仅允许创建其自身的一个实例,并提供对该实例的访问权限。它包含静态变量,可以容纳其自身的唯一和私有实例。它被应用于这种场景——用户希望类的实例被约束为一个对象。在需要单个对象来协调整个系统时,它会很有帮助。
1、单例类只能有一个实例
2、单例类必须自己创建自己的唯一实例
3、单例类必须给其他所有对象提供这一实例
1.尽量使用懒加载
2.双重检索实现线程安全
3.构造方法为private
4.定义静态的Singleton instance对象和getInstance()方法
单例模式至少有六种写法。
作为一种重要的设计模式,单例模式的好处有:
1、控制资源的使用,通过线程同步来控制资源的并发访问
2、控制实例的产生,以达到节约资源的目的
3、控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。但其实通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。
虽然也是只有一个线程能够执行,假如线程B先执行,线程B获得锁,线程B执行完之后,线程 A获得锁,但是此时没有检查singleton是否为空就直接执行了,所以还会出现两个singleton实例的情况。
既然懒汉式是非线程安全的,那就要改进它。最直接的想法是,给getInstance方法加锁不就好了,但是我们不需要给方法全部加锁啊,只需要给方法的一部分加锁就好了。基于这个考虑,引入了双检锁(Double Check Lock,简称DCL)的写法:
使用volatile 的原因:
对于JVM而言,它执行的是一个个Java指令。在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间, 然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就使出错成为了可能,我们仍然以A、B两个线程为例:
加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。
枚举类实现单例模式是 effective java 作者极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。因为枚举类没有构造方法,可以防止反序列化操作。
1、除枚举方式外, 其他方法都会通过反射的方式破坏单例,反射是通过调用构造方法生成新的对象,所以如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例, 则阻止生成新的实例,解决办法如下:
2、如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例,所以我们可以不实现序列化接口,如果非得实现序列化接口,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象。
Runtime是一个典型的例子,看下JDK API对于这个类的解释"每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接,可以通过getRuntime方法获取当前运行时。应用程序不能创建自己的Runtime类实例。",这段话,有两点很重要:
1、每个应用程序都有一个Runtime类实例
2、应用程序不能创建自己的Runtime类实例
只有一个、不能自己创建,是不是典型的单例模式?看一下,Runtime类的写法:
为了节约系统资源,有时需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,我们无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,我们可以通过单例模式来实现。
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
关于单例模式的漫画分析: https://mp.weixin.qq.com/s/f-sJIZHr7JUa31gKTllSFQ
单例模式的优缺点、注意事项、使用场景
文章评论