Singleton Pattern -- 单例模式
Singleton Pattern — 单例模式
单例模式是用来创建一个只能又一个实例的对象。
单例模式类图如下。
单例模式通用代码(饿汉模式):
public class Singleton { private static Singleton singleton = new Singleton(); // 限制产生多个对象 private Singleton() {} //通过该方法获取实例对象 public static Singleton getInstance() { return singleton; } //类中其他方法,尽量是static public static void doSomething() {} }
延时单例模式(懒汉模式)
public class Singleton { private static Singleton singleton = null; // 限制产生多个对象 private Singleton() {} //通过该方法获取实例对象 public static Singleton getInstance() { if(singleton == null) { singleton = new Singleton(); } return singleton; } //类中其他方法,尽量是static public static void doSomething() {} }
单例模式和线程
以上两种模式都属于线程不安全,在不是高并发的条件下,基本不会出现问题,但是当并发量增加时可能回出现多个实例,破坏最处当预期。
为什么会出现这种情况呢?
如果一个A执行到singleton = new Singleton(),但还没有获得对象(对象初始化时需要时间的),在此同时第二个线程B也在执行,执行到
(singleton == null)判断,那么线程B判断的结果也为真。于是继续运行下去,线程A获得了一个对象,线程B也获得了一个对象,
在内存中就出现了两个对象!
1、只要在getInstance() 方法前加 synchronized 关键字就能解决该问题。
public class Singleton { private static Singleton singleton = null; // 限制产生多个对象 private Singleton() {} //通过该方法获取实例对象 //通过添加synchronized关键字到getInstance()方法中,迫使每个线程 //访问该方法之前,要等其他线程从该方法中离开。 public static synchronized Singleton getInstance() { if(singleton == null) { singleton = new Singleton(); } return singleton; } //类中其他方法,尽量是static public static void doSomething() {} }
解决了线程安全问题,但是使用同步会降低性能,(如果你的系统能接受getInstance()方法带来的额外性能负担,这样的做法简单有效。)
同步可能造成执行效率下降100倍。因此,如果getInstance()的程序使用非常频繁就得重新考虑了。
2、用“双重检查加锁”,在getInstance中减少使用同步。
利用双重检查加锁(double-checkd locking),首先检查是否有实例已创建,如果尚未创建,
“才”进行同步,这样一来只有第一次会进行同步,这正是我们需要的。
public class Singleton { //volatile关键字确保:当singleton变量被初始化成 //Singleton实例时,多个线程正确当处理singleton变量。 private volatile static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if(singleton == null) {//检查实例,如果不存在就进入同步区块。 //只有第一次才会彻底执行此处代码。 synchronized (Singleton.class) { //进入区块再做一次判读,如果位null才会创建。 if(singleton == null) { singleton = new Singleton(); } } } return singleton; } //类中其他方法,尽量是static public static void doSomething() {} }
如果你关心性能,这种做法可以大大减少getInstance()的耗时。
注意:双重检查加锁不实用1.4以前的Java。
Java1.2前会造成,单利在没有全局的引用时被当作垃圾回收掉。Java1.2以后这个问题已被修复了。