说说单例模式

单例模式应该都不陌生,被广泛使用的设计模式之一,在应用这个模式时,单例对象的类必须保证只有一个实例存在。

下面将会为大家介绍几种单例实现的方式,虽然实现方式有差异,但是核心原理都是:

1、将构造函数私有化

2、通过静态方法获取一个唯一的实例

3、在获取过程中保证线程安全

4、防止反序列化导致重新生成实例对象

线程不安全的单例

这种实现方式是线程不安全的,非常不推荐这种做法

public class Signleton {
	private  static Signleton signleton;

	private Signleton() {
	}

	/**
	 * 第一中方式:缺点,多线程访问时不安全
	 * 
	 * @return
	 */
	public static Signleton getInstance() {
		if (signleton == null) {
			signleton = new Signleton();
		}
		return signleton;
	}

	/**
	 * 加入该方法杜绝在反序列化时重写生成对象
	 *
	 * @return
	 * @throws ObjectStreamException
	 */
	private Object readResolve() throws ObjectStreamException {
		return signleton;

	}

}

线程安全的单例

这种方式是线程安全的,但是每次访问getInstance()方法时都会进行同步控制,效率低

public class Signleton {
	private  static Signleton signleton;

	private Signleton() {
	}


	/**
	 * 第二中方式 加上同步关键字,解决了多线程访问安全的问题
	 * 缺点:会降低性能,因为一旦设置好signleton变量,就不需要同步了,之后每次调用该方法会降低性能
	 * 
	 * @return
	 */
	public static synchronized Signleton getInstance() {
		if (signleton == null) {
			signleton = new Signleton();
		}
		return signleton;
	}

	
	/**
	 * 加入该方法杜绝在反序列化时重写生成对象
	 *
	 * @return
	 * @throws ObjectStreamException
	 */
	private Object readResolve() throws ObjectStreamException {
		return signleton;

	}

}

双重锁的单例模式

该方式避免了每次调用getInstance()方法都进行同步控制

public class Signleton {
	private  static Signleton signleton;
	private Signleton() {
	}


	/**
	 * 第三种方式, 双重检查加锁,首先检查实例是否已经创建,如果未创建才进行同步控制,
	 *  这样只有第一次会同步,提高了性能
     *  这种方式是错误的!!!!!或导致错误的结果,请看文章最后   
	 * 
	 * @return
	 */
	//public static Signleton getInstance() {
	//	if (signleton == null) {
	//		synchronized (Signleton.class) {
	//			if (signleton == null) {
	//				signleton = new Signleton();
	//			}
	//		}
	//	}
    //	return signleton;
	//}

	/**
	 * 加入该方法杜绝在反序列化时重写生成对象
	 *
	 * @return
	 * @throws ObjectStreamException
	 */
	private Object readResolve() throws ObjectStreamException {
		return signleton;

	}

}

但是由于signleton=new Signleton()不是原子操作,由于线程调度的原因会在某些情况下出现失效的问题,不过只需要加上volatile即可(private volatile static Signleton signleton;)。

饿汉单例模式

该方式会在类加载时就初始化signleton。

public class Signleton {
	 private static Signleton signleton=new Signleton();//对应第四中方式

	private Signleton() {
	}

	 /**
	 * 第四种方式,如果应用程序总是创建并使用单例实例,可以在镜头初始化器中创建单例, 使用时直接返回
	 *
	 * @return
	 */
	public static Signleton getInstance() {

		return signleton;
	}
	
	/**
	 * 加入该方法杜绝在反序列化时重写生成对象
	 *
	 * @return
	 * @throws ObjectStreamException
	 */
	private Object readResolve() throws ObjectStreamException {
		return signleton;

	}

}

静态内部类单例模式

该模式在类加载时不会初始化signleton,只有在调用get方法时才初始化

public class Signleton {
	
	/**
	 * 第五种方式,使用静态内部类实现单例,相比饿汉模式不会在第一次加载Signleton类时就初始化sInstance,
	 * 只有在调用getInstance时才会初始化
	 */

	public static Signleton getInstance() {
		return SignletonHolder.sInstance;
	}

	private static class SignletonHolder {
		private static final Signleton sInstance = new Signleton();
	}

	/**
	 * 加入该方法杜绝在反序列化时重写生成对象
	 *
	 * @return
	 * @throws ObjectStreamException
	 */
	private Object readResolve() throws ObjectStreamException {
		return SignletonHolder.sInstance;

	}

}

枚举单例

该方式写法简单,且默认枚举实例的创建时线程安全的,并且在任何情况下它都是一个单例

public enum SingletonEnum {
		INSTANCE;
		// TODO 一些类的方法
		public void doSomthing() {

		}
	}

第三种方式存在的问题:

public class DoubleCheckedLocking {                      // 1
    private static Instance instance;                    // 2
    public static Instance getInstance() {               // 3
        if (instance == null) {                          // 4:第一次检查
            synchronized (DoubleCheckedLocking.class) {  // 5:加锁
                if (instance == null)                    // 6:第二次检查
                    instance = new Instance();           // 7:问题的根源出在这里
            }                                            // 8
        }                                                // 9
        return instance;                                 // 10
    }                                                    // 11
}

在线程执行到第4行,代码读取到instance不为null时,instance引用的对象有可能还没有完成初始化

instance= new Instance()  可以分解为三步

memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory);  // 2:初始化对象
instance = memory;    // 3:设置instance指向刚分配的内存地址

2和3在某些JIT编译器上是可以被重排序的,这样会导致这样的可能性,线程1正在执行第7步时,线程B执行了第四步instannce!=null。线程B将访问instance引用的对象,由于2,3进行了重排序,导致线程B访问到了一个未初始化的对象

使用volatile禁止2,3的重排序(JDK1.5以后增强了volatile的语义)

public class SafeDoubleCheckedLocking {
    private volatile static Instance instance;
    public static Instance getInstance() {
        if (instance == null) {
            synchronized (SafeDoubleCheckedLocking.class) {
                if (instance == null)
                    instance = new Instance();         // instance为volatile,现在没问题了
            }
        }
        return instance;
    }
}

猜你喜欢

转载自blog.csdn.net/fuckluy/article/details/77619910