一、饿汉式
饿汉式比较简单,在程序编译期间完成初始化操作,无线程安全问题,使用可靠。唯一的
缺点是 内存,不管程序是否用得到,先完成实例化。日常开发中推荐使用,
这样像瓜式的编程显然是不够的,不管是面试的需要,还是提高代码的质量,都需要学习懒汉式。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
//获取单例实例
public static Singleton getInstance() {
return instance;
}
}
二、懒汉式
懒汉式 名义是在需要的时候实例化对象,这种延后切治带来的好处式加快项目的启动和节约内存。当然延后治化也带来了诸多问题,需要开发者逐一来解决。
案例1
本实现看似解决了懒汉式延后实例化的问题,但是存在 线程问题 ,即在多线程环境下,存在单例不唯一的情况。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
//获取单例实例
public static Singleton getInstance() {
if(null == instance ){
instance = new Singleton();
}
return instance;
}
}
案例2
针对上述存在的线程问题,很容易想到的是使用 synchronized 锁来控制并发,从而回避线程问题。单例不唯一的问题是解决了,然而却带来了并发性能问题.
缺点:
当多线程环境下获取单例实例时,由于 getInstance 方法是加锁的(阻塞锁),并发访问转化为串行访问,性能严重降低。对于一名有追求的技术人怎么能凑合呢,必须继续优化!
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
//获取单例实例
public synchronized static Singleton getInstance() {
if(null == instance ){
instance = new Singleton();
}
return instance;
}
}
案例3 : 双重检查机制,降低锁的颗粒度,解决并发时的性能问题
当通过 getInstance 方法获取单例实例对象时,如果不为 NULL,则直接返回,如果为 NULL.线程到这里转化为顺序执行。
第一个获取锁的线程通过 new 创建新实例然后释放锁,后续线程检查实例已经不为NULL ,直接返回,无需重复实例。虽然已经相对完善
缺点:并发场景下会存在异常。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
//获取单例实例
public static Singleton getInstance() {
if(null == instance ){
synchronized(Singleton.class){
if(null == instance ){
instance = new Singleton();
}
}
}
return instance;
}
}
案例4:
上述实现仍然存在一点瑕疵:在并发场景下,仍然会存在某一刻出现 运行异第 的情况。这也是给程序员幻觉的根本原因,代码像抽筋了似的,不知道怎么报错了,也不知道怎么变好了,
问题产生的根源是 Java指今重排,更具体地说是通过 new 关键字创建对象时指令重排,应对方式是通过 volatile 关键字抑制指令重排。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
//获取单例实例
public static Singleton getInstance() {
if(null == instance ){
synchronized(Singleton.class){
if(null == instance ){
instance = new Singleton();
}
}
}
return instance;
}
}