摘要:单例模式是建立类型中经常使用的一种设计模式。该模式下的类有且仅有一个实例。
说到单例模式,其实你们应该都不陌生,由于真的太经常使用了,应该全部开发者接触设计模式的第一个模式。那我这里一句话简单说下为什么使用单例:若是你但愿你的某个类只须要有一个实例对象,而且全局共享,那么你就使用单例。java
单例模式是建立类型中经常使用的一种设计模式。该模式下的类有且仅有一个实例。单例模式常见的实现有懒汉式、饿汉式这两种方式,可是在这里,我不想讨论这两种方式,由于常见因此没有讨论和须要思考的价值。web
让咱们来看看如下的几种方式的一些实现机制:数据库
上代码:编程
开发、单例模式、线程、segmentfault
DCL双重加锁的方式保证每次调用getSingleton方法的时候都是同步的。其实加锁你们都能理解,就是解决多线程同步的问题。但其实这里有个重点,就是这行代码:设计模式
private volatile static Singleton singleton
tomcat
为何要用volatile去修饰呢,这边从两个方面去说明:安全
1.若是不用volatile修饰会怎么样?多线程
这看起来彷佛也是行的通的,可是了解过编译器和程序指令的话就会知道那是不可靠的,具体缘由以下:框架
简单理解,那就是如今都0202年了,一台计算机cpu和内核都是好几个出现的,不在是那个单核的老时代了,因此java文件编译成字节码指令以后,你的编码逻辑确实是串行的,计算机也会根据范式把你编程的逻辑结果给你执行返回,可是具体到cpu去执行指令的时候,为了体现多核的优点,会对一些指令作并行处理,以加快程序运行速度。
我想好奇的你仍是想知道,若是不加volatile的话,会在何时出现问题,那我给你说说问题出现的顺序:
至于线程B返回以后会发生什么,可想而知,没实例化完,那么就会致使调用部分的方法的时候,就会有空指针的异常,因此就是我上面说的,不可靠。
2.volatile做用是啥?
为了解决这个问题,JDK1.6以后的版本提供了该关键字, 其实就是为了让其修饰的变量你可以在线程间可见,而所谓的可见,那就是你们都从主存中获取,至于主存等概念在这里就不展开说明了。
能够这么理解:在线程B读取volatile变量后,线程A在写这个volatile以前,全部可见的共享变量的值都将当即变得对线程B可见。
对应上面的问题解决也就是:线程A在未初始化完,singleton变量那就是null,线程B读到的也就是null,那么当线程B再进去想要加锁实例化的时候,发现线程A获取了锁正在实例化,那就阻塞了起来,直到A实例化完释放锁,可是由于实例化完以后B立马又知道该变量不为null了因此在第二个判断的时候,就不用进去new了,返回了。
上代码:
静态内部类是一个我比较喜欢的实现方式,固然很明显代码少,逻辑较为简单。这种方式主要是利用了classloader机制来保证初始化singleton的时候只有一个线程,避免了须要再去保证线程同步的问题。同时咱们把这种方式实例化有lazy loading的效果,其实主要是由于静态内部类Holder类并不会在Singleton类被装载的时候就被初始化了,只有当Holder类被主动使用,也就是调用了getSingleton方法以后,才会显示的装载Holder类,从而实例化singleton对象。若是singleton对象是一个消耗资源占用比较大的内存的对象的时候,若是你但愿延迟加载的话,那么这种方式是个不错的选择。
可是其实静态内部类的方式实际上并无想象中的那么完美,由于它没法阻挡反射和反序列攻击,你能够利用前面两种方式再去构造新的Singleton的实例,因此不是严格意义上的单例。
上代码:
这种方式是Josh Bloch提倡的,利用枚举的特性,让JVM来保证线程安全和单例的问题,还能防止反序列化和反射,除了你们不怎么经常使用外,其实这种简单的方式是个很好的方式。
反编译看一下,其实枚举是在static块中进行的对象的建立:
优势:
1.提供了惟一实例的受控访问。
2.由于只有一个实例,节约了系统资源,提升系统性能。
缺点:
1.单例模式没有抽象层,扩展比较困难。
2.单例类的职责太重,违背了“单一职责原则”。
个人推荐:
咱们去使用单例基本目标就是为了节省内存资源,并且通常的web项目都会引入Spring框架,经过Spring实现的单例和上面设计模式说的单例有所不一样。设计模式的单例是在整个Java应用中只有一个实例,而Spring中的单例是在一个IOC容器中就只有一个单例。但对于web应用来讲,web容器(Jetty或tomcat)对用户的每一个请求都会建立一个单独的servlet线程去处理请求,Spring框架下的接口每一个action也都是单例的,那么其实就保证了咱们使用的是一个实例。
同时Spring也支持咱们经过注解或者xml进行lazy-init,也能够指定scope肯定其是否为全局单例,又或者是多个实例,对于程序来讲有了更多的选择。
固然上面提到的线程安全的问题,其实大多数状况下Spring是没有去保证全部bean的线程安全,因此主动权交给了开发者,咱们本身编写程序要保证线程安全的。不过在咱们常用的数据库dao层的那些dao 的bean对象,Spring经过ThreadLocal对象,区别与咱们经常使用的加锁的方式而是用空间换时间,给每一个线程分配了独自的变量副本,从而隔离了多线程访问对数据访问的冲突,保证了线程安全性。至于这个类和这个机制,这里就不展开谈了,谈多了这篇文章就装不下了。