浅谈单例设计模式探索
来源:互联网 发布:java if else 都执行 编辑:程序博客网 时间:2024/05/21 22:09
最近在和朋友聊技术时,讨论到了设计模式,发现简单的单例设计模式并不简单,多线程是我们在JAVA开发中常见的场景,在多线程环境下接触的单例模式可能并不单例,下面来详细学习一下单例模式。
一、什么是单例模式
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
二、单例模式的应用场景
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实例.
总结以上,不难看出:
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
三、懒汉及饿汉单例模式
1.单例模式的饿汉式
public class Singleton { private static Singleton instance=new Singleton(); private Singleton(){}; public static Singleton getInstance(){ return instance; } }
优点:从它的实现中我们可以看到,这种方式的实现比较简单,在类加载的时候就完成了实例化,避免了线程的同步问题。
缺点:由于在类加载的时候就实例化了,所以没有达到Lazy Loading(懒加载)的效果,也就是说可能我没有用到这个实例,但是它
也会加载,会造成内存的浪费(但是这个浪费可以忽略,所以这种方式也是推荐使用的)。
2.单例模式的懒汉式(线程不安全)
public class Singleton { private static Singleton instance=null; private Singleton() {}; public static Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; } }
三、多线程环境下的单例模式探索
由于饿汉式单例模式会造成部分资源的浪费,因此看起来使用懒汉式单例模式看起来更为合理
但当你指出上面的懒汉式单例模式这段代码在超过一个线程并行被调用的时候会创建多个实例的问题时,他很可能会把整个getInstance()方法设为同步(synchronized),代码如下:
public static LazySingleton getInstance(){if (instance == null){ synchronized (LazySingleton.class){instance = new LazySingleton(); } }return instance;}效率较低,多线程环境下并不一定能保证单例
问题貌似得以解决,事实并非如此。如果使用以上代码来实现单例,还是会存在单例对象不唯一。原因如下:
假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(instance == null)判断,这种方式称为双重检查锁定(Double-Check Locking)。
public class Singleton { /** * 懒汉式变种,属于懒汉式中最好的写法,保证了:延迟加载和线程安全 */ private static volatile Singleton instance=null; private Singleton() {}; public static Singleton getInstance(){ if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。
四、内部类及枚举类
1.内部类
public class Singleton{ private Singleton() {}; private static class SingletonHolder{ private static Singleton instance=new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.instance; }
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同
的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时
并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是
无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
2.枚举
public enum SingletonEnum { instance; private SingletonEnum() {} public void method(){ } }
调用方式:
SingletonEnum.instance.method();
可以看到枚举的书写非常简单,访问也很简单在这里SingletonEnum.instance这里的instance即为SingletonEnum类型的引用所以得到它就可以调用枚举中的方法了。
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过,这种方式也是最好的一种方式,如果在开发中JDK满足要求的情况下建议使用这种方式。
五、饿汉式和懒汉式单例模式比较
饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。
懒汉式单例类在第一次使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理好多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的机率变得较大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响。
附上学习时手写demo:
/** * 单例设计模式的衍生 */class Singleton { private volatile static Singleton _instance; private Singleton() { // preventing Singleton object instantiation from outside } /** * version 1.0 * 懒汉式单例模式 * 多线程访问时可能会创建多个实例 * 违反单例原则 * 适合单线程使用 */ public static Singleton getInstance() { if (_instance == null) { _instance = new Singleton(); } return _instance; } /** * version 2.0 * 多线程访问时仍然只会创建一个实例 * 是线程安全的 * 但是犹豫加入了synchronized同步关键字 控制并发锁 * 造成了性能的不必要开销和浪费 * 不是最佳模式 */ public static synchronized Singleton getInstanceTS() { if (_instance == null) { _instance = new Singleton(); } return _instance; } /** * version 3.0 * 双重检查锁定单例模式 * 代码会检查两次单例类是否有已存在的实例,一次加锁一次不加锁,一次确保不会有多个实例被创建 * 多线程环境下使用 * 目的是最小化同步的成本和提高性能 * 只锁定关键部分的代码 * 必须使用volatile 修饰实体类 */ public static Singleton getInstanceDC() { if (_instance == null) { synchronized (Singleton.class) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } /** * version 4.0 * 静态单例对象没有作为Singleton的成员变量直接实例化, * 因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass, * 在该内部类中定义了一个static类型的变量instance, * 此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。 * 由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。 */ private static class HolderClass { private final static Singleton instance = new Singleton(); } public static Singleton getInstanceMS() { return HolderClass.instance; }}
参考文章:
设计模式之——单例模式(Singleton)的常见应用场景
Java设计模式—单例设计模式(Singleton Pattern)完全解析
如何在Java中使用双重检查锁实现单例
- 浅谈单例设计模式探索
- 探索设计模式--单例模式
- 【探索】 - 设计模式之单例模式
- 探索设计模式之----单例模式
- 单例设计模式浅谈
- 浅谈单例设计模式
- 单例设计模式 浅谈
- 浅谈单例设计模式
- 设计模式浅谈之单例模式
- 浅谈设计模式----单例模式
- 设计模式浅谈--单例模式
- 浅谈设计模式之单例模式
- 浅谈设计模式之单例模式
- Java-设计模式-单例模式浅谈
- 浅谈:单例设计模式(Singleton)
- 结合Unity浅谈设计模式-单例
- 探索设计模式之六——单例模式
- 探索设计模式之六——单例模式
- iframe 子页面与父页面通信
- HDU-1532(网络最大流)
- IO流——利用字节流复制图片
- 获取客户端真实IP
- 断路器(Hystrix)
- 浅谈单例设计模式探索
- js获取css值的方法:style、getComputedStyle和currentStyle
- 使用Python画股票的K线图
- 利用 Disk Drill 导出 iOS App 沙盒中的数据
- BZOJ4196: [Noi2015]软件包管理器 (好题
- IOC详解
- 元素互换移位算法
- HTML5_兼容-全局与局部的区别
- mac 断电后重启,有线网络显示连接但是不能上网