Java设计模式——单例模式

单例模式

1. 概念:单例模式是一种对象创建型的设计模式,该模式可以保证一个类有且只有唯一一个实例对象。 GoF对该模式的定义是:保证一个类只有一个实例存在,并且同时提供能对该实例进行访问的全局方法。
2. 实现方式及分析

(1)饿汉式

public class Singleton{
	private static final Singleton INSTANCE = new Singleton();
	private Singleton(){}
	public static Singleton getInstance(){
		return INSTANCE;
	}
}

分析:写法简单,在类加载阶段就完成了实例化,避免了线程同步的问题;但是没有达到懒加载的效果,如果程序中一直没有使用到这个类,则会造成内存浪费。
(2)懒汉式

public class Singleton{
	private static final Singleton INSTANCE;
	private Singleton(){}
	public static Singleton getInstance(){
		if(INSTANCE == null){
			INSTANCE = new Singleton();
		}
		return INSTANCE;
	}
}

分析:相比于上一种实现方法,达到了懒加载效果;但是这种方式只能使用在单线程环境下,一个线程进入了if (INSTANCE == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
(3)懒汉式2.0(使用同步方法保证线程安全)

public class Singleton{
	private static final Singleton INSTANCE;
	private Singleton(){}
	public static Synchronized Singleton getInstance(){
		if(INSTANCE == null){
			INSTANCE = new Singleton();
		}
		return INSTANCE;
	}
}

分析:为解决上一种实现方案线程不安全的问题,解决方案就是做个线程同步就可以了,于是就对getInstance()方法进行了线程同步,即对getInstance()方法加同步锁,这就保证了单例的唯一性,但是缺效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步
(4)双重检查式

public class Singleton{
	private static final Singleton INSTANCE;
	private Singleton(){}
	public static Singleton getInstance(){
		if(INSTANCE == null){
			Synchronized(Singleton.class){
				if(INSTANCE == null){
					INSTANCE = new Singleton();}
			}
		}
		return INSTANCE;
	}
}

分析:表面上来看,在执行该代码时,先判断INSTANCE 对象是否为空,为空时再进行初始化对象。即使是在多线程环境下,因为使用了Synchronized锁进行代码同步,该方法也仅仅创建一个实例对象。但是,从根本上来说,这样写还是存在一定问题的。
下面具体分析这个问题:
上述代码标号为①的代码功能是是创建实例对象,可以分解为如下伪代码步骤:
a.分配对象的内存空间
b.初始化对象
c.设置INSTANCE 指向刚分配的内存地址
其中b,c两个步骤可能会出现指令重排现象,执行顺序变成a,c,b。这样会出现的问题是:如果A线程执行了a,c两步后,线程B开始执行到b步骤,此时进入第一次 if 判断后,INSTANCE不为null,直接返回INSTANCE,但此时对象并没有被初始化,B线程就会抛出对象为初始化的异常。
对应的解决办法如下:两种实现方式
(5)不让b,c两步重排序(使用volatile关键字禁止多线程下JVM指令重排)

public class Singleton{
	private static final volatile Singleton INSTANCE;
	private Singleton(){}
	public static Singleton getInstance(){
		if(INSTANCE == null){
			synchronized(Singleton.class){
				if(INSTANCE == null){
					INSTANCE = new Singleton();}
			}
		}
		return INSTANCE;
	}
}

(6)允许b和c进行重排序,但排序之后,不允许其他线程看到。
静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。该方案的实质是,允许b和c进行重排序,但不允许非构造线程(此处是B线程)“看到”这个重排序。

public class Singleton{
	public static class SingleHolder(){
	    private static final Singleton INSTANCE = new Singleton();
	  }
	  public static Singleton getInstance(){
	      return SingleHolder.INSTANCE;
	  }
}

分析:上面这种实现方式虽然没有显示的使用Synchronized,但底层还是使用了Synchronized。是接住了ClassLoader类的线程安全机制,即是ClassLoader中的loadClass方法在加载类的时候使用了Synchronized,除非被重写,这个方法默认在整个加载过程中都是同步的,也就是保证了线程安全。

下面介绍一种利用CAS无锁操作实现

多个线程尝试使用CAS同时改变一个变量时,只有其中一个线程能改变变量的值,而其他线程都失败,但这些线程不会挂起,而是被通知这次竞争失败,可以再次尝试忙等待,没有线程切换和阻塞的额外消耗,但是如果一直执行不成功,会造成CPU很大的消耗。

public class Singleton {
    private static final AtomicReference<Singleton> INSTANCE= new AtomicReference<>();
    private Singleton(){}
    public static final Singleton getInstance(){
        for (;;){
            Singleton current = instance.get();
            if(current != null)
                return current;
            current = new Singleton();  //如果多个现成执行到这里的话,会有大量对象创建,有可能导致内存溢出
            if(instance.compareAndSet(null,current))
                return current;
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值