设计模式(2)---单例模式

上一篇:设计模式基本概述

1.什么是单例模式?

  单例模式是java种最简单的设计模式之一,它提供了一种创建对象的最佳方式。此种设计模式保证一个类只有一个实例,并且提供一个访问该实例的全局访问点(就像是一个学校只有一个校长)。

在这里插入图片描述

2.单例模式的优点?

  1.单例模式因为在内存中只有一个实例,避免了频繁地创建实例、销毁实例,所以极大地节省了系统资源地开销。

  2.设置全局访问点,优化共享资源访问。

3.常见地单例模式?

1.饿汉式

  此种设计模式线程安全,调用效率高,不能够延时加载

/**
 * 饿汉单例模式
 */
public class Singleton01 {

	//让构造函数私有化,这样就不会被实例化
	private Singleton01() {
	}

	//创建Singleton对象(因为使用了static关键字,所以类加载地时候就会被初始化,所以不会有线程安全问题)
	private static Singleton01 instance = new Singleton01();

	//获取唯一可用对象,因为线程安全,所以不需要使用synchronized关键字,效率更高
	public static Singleton01 getInstance() {
		return instance;
	}
}

  饿汉式不管这个对象用不用,反正类已加载地时候就会创建,如果想要在使用地时候再创建此对象,则可以使用懒汉式。

2.懒汉式

  此种设计模式线程不安全(可用synchronized解决),调用效率不高,能够延时加载

/**
 * 懒汉式
 *
 * 懒嘛!要用地时候才去创建对象
 */
public class Singleton02 {

	//构造方法私有化
	private Singleton02() {
	}

	//类初始化地时候,不立即加载对象
	private static Singleton02 instance;

	/**
	 *给一个公共地获取对象地方法(懒汉式本来线程不安全,但是可以使用synchronized关键字保证线程安全,
	 * 
	 * 也正是使用了synchronized关键字使得调用效率降低
	 */
	public static synchronized Singleton02 getInstance(){
		if(instance==null){
			instance=new Singleton02();
		}
		return instance;
	}
}

代码注释中说了这种懒汉式的单例虽然可以使用synchronized关键字使得线程安全,但是效率降低了,为了提高效率,则可以使用DCL( double-checked locking)双重校验锁

3.DCL( double-checked locking)双重校验锁
/**
 * DCL双重校验锁
 */
public class Singleton03 {

	//1.构造方法私有化
	private Singleton03() {
	}

	//2.类初始化的时候,不立即加载对象
	private static Singleton03 instance;

	//3.给个获取该对象的公共方法

	public static Singleton03 getInstance() {
		//判断instance是否为空
		if (instance == null) {
			//instance为空立即上锁
			synchronized (Singleton03.class) {
				//再次判断instance是否为空
				if (instance == null) {
					instance = new Singleton03();
				}
			}
		}
		return instance;
	}

}

DCL双重校验锁不是上来就直接将整个publice方法给锁上而是将锁更加细化,不仅保证了对象在使用的时候才去创建而且提高了效率。但是!

  DCL这种方式因为JMM(java内部模型)的原因,偶尔可能会出问题:

因为不是原子性操作,在内存中一般会经历下面3步:

1)分配内存
2)执行构造方法
3)指向地址

而在执行代码的时候,为了提高性能,编译器和处理器常常会对指令进行重排序。重排序分为三种:编译器重排序,指令级并行重排序,内存系统重排序,实现优化,优化结果可能是

1)初始化 Singleton4 对象;

2)把 Singleton4 对象地址赋给instance变量

也有可能是这样

1)初始化一半Singleton4对象;

2)把Singleton4对象地址赋给instance变量

3)初始化剩下的Singleton4对象;

如果是第二种,则DCL在多线程的情况下可能出现的情况:当第一个线程判断instance==nulltrue,进入synchronized内,执行instance=new Singleton03()的时候,初始化了一般就将地址赋给instance变量,此时第二个线程判断instance==null的时候发现instance不为null,然后执行了return instance,而实际上此时第一个线程可能正在执行剩下的instance=new Singleton03()

我们可以使用volatile关键字能够大幅避免此种情况,但是也只能避免指令重排,继续优化有一种静态内部类式

4.静态内部类式
/**
 * 静态内部类式
 */
public class Singleton04 {

	//构造方法私有化
	private Singleton04() {
	}

	//创建静态内部类
	private static class InnerClass {
		//在静态内部类中创建对象实例化
		private static final Singleton04 instance=new Singleton04();
	}

	public static Singleton04 getInstance(){
		return InnerClass.instance;
	}
}

  此种方式:外部类没有静态属性,不会在类加载的时候就初始化,只有调用了getInstance()方法的时候才回去加载静态内部类,加载的时候线程安全所以不用考虑线程安全问题。所以此种模式又保证了线程安全,又符合lazy loading(延时加载)

  到了第4种方式应该说已经属于非常优秀的了,但是java中有一种非常牛X的机制叫反射,它能够无视你是否是private的,一得就能得到!这也导致了静态内部类式也可能被破坏单例;

/**
 * 静态内部类式
 */
public class Singleton04 {

	//构造方法私有化
	private Singleton04() {
	}

	//创建静态内部类
	private static class InnerClass {

		//在静态内部类中创建对象实例化
		private static final Singleton04 instance = new Singleton04();
	}

	public static Singleton04 getInstance() {
		return InnerClass.instance;
	}

}

class test {

	public static void main(String[] args) throws Exception {
		Singleton04 instance = Singleton04.getInstance();

		//使用反射破坏单例
		//通过class对象获取构造器
		Constructor<Singleton04> singleton04Constructor = Singleton04.class.getDeclaredConstructor(null);
		//设置无视private
		singleton04Constructor.setAccessible(true);
		//创建一个实例
		Singleton04 singleton04 = singleton04Constructor.newInstance();

		boolean result = instance == singleton04;
		System.out.println("两个实例对象是否一样:" + result);
	}
}

在这里插入图片描述
从结果来看,使用反射直接破坏了单例,那么之前的几种单例实现方式也都可以通过反射进行破坏。

为了防止通过反射来破坏单例,我们可以使用枚举

5.枚举

  枚举是在JDK1.5开始引入的,它线程安全、不能延时加载、调用效率高

在这里插入图片描述
我们通过反射创建对象实例的newInstance()方法的源码中就说了如果是枚举会抛出异常!
在这里插入图片描述
所以枚举是一种比较推荐的单例模式写法

/**
 * 枚举
 */
public enum Singleton05 {
	INSTANCE;

	public Singleton05 getInstance() {
		return INSTANCE;
	}
}

/**
 * 测试
 */
class Test {

	public static void main(String[] args) {
		Singleton05 instance1 = Singleton05.INSTANCE;
		Singleton05 instance2 = Singleton05.INSTANCE;

		boolean result = instance1 == instance2;
		System.out.println("两个实例是否一致:" + result);

	}
}

下一篇:工厂模式

原文地址:https://www.cnblogs.com/wgty/p/12810440.html