单例模式

介绍

类的单例设计模式,就是采取一定的方法来保证在整个软件系统中,某个类只存在一个对象实例。且该类只提供一个取得其对象实例的方法(静态方法)。

八种方式

单例模式有八种方式:

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

步骤

步骤大致如下:

  1. 构造器私有化(防止外部new)
  2. 类的内部创建对象
  3. 向外提供一个静态公共方法(getInstance)

饿汉式(静态常量)

代码:

package singleton.type1;

public class Singleton {

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

测试代码:

package singleton.type1;

public class SingletonTest {

	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2);
		System.out.println(instance.hashCode());
		System.out.println(instance2.hashCode());
	}

}

测试结果:

true
2018699554
2018699554

说明

  1. 优点:写法简单,类装载时就完成了实例化。基于class loader机制避免了线程同步问题
  2. 缺点:类装载时就完成了实例化,没有懒加载的效果。如果从始至终并未使用该实例,则造成内存浪费

结论:此模式可用,可能造成内存浪费

饿汉式(静态代码块)

代码:

package singleton.type2;

public class Singleton {

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

测试代码和测试结果同上

说明

  1. 这种方式和上面类似,只不过将类实例化的过程放在了静态代码块中。在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点同上。

结论:此模式可用,可能造成内存浪费

懒汉式(线程不安全)

代码:

package singleton.type3;

public class Singleton {

	private Singleton() {
		
	}
	
	private static Singleton instance;
	
	public static Singleton getInstance() {
		if(null == instance) {
			instance = new Singleton();
		}
		return instance;
	}
	
}

说明

  1. 优点:起到了懒加载效果,只在需要的时候才实例化对象
  2. 缺点:只能在单线程下使用。如果在多线程下,一个线程进入了if(null == instance)的判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时就会产生多个实例。所有在多线程环境下不可使用该方式。

结论:实际开发中,不要用这种方式

懒汉式(线程安全,同步方法)

代码:

package singleton.type4;

public class Singleton {

	private Singleton() {
		
	}
	
	private static Singleton instance;
	
	public static synchronized Singleton getInstance() {
		if(null == instance) {
			instance = new Singleton();
		}
		return instance;
	}
	
}

说明

  1. 优点:解决了线程安全问题
  2. 缺点:效率太低了。每个线程想获得类的实例对象时,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面想获取该类实例,直接返回就行了。方法同步效率太低

结论:实际开发中,不推荐使用这种方式

懒汉式(线程安全,同步代码块)

代码:

package singleton.type5;

public class Singleton {

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

说明

  1. 该方式本意是对第四张方式的改进,因为同步方法效率太低,改为同步产生实例的代码块
  2. 但是,这种方式并不能起到线程同步的作用。这里其实和第三种问题一样,加入一个线程进入了if(null == instance)语句块,还未执行下面的代码,另一个线程也通过了这个判断语句,还是会产生多个实例

结论:实际开发中, 不能使用这种方式

双重检查

代码:

package singleton.type6;

public class Singleton {

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

这里需要额外说明一下,volatile关键字使得某个线程对某个对象(变量)的修改,其他线程能立刻感知到。

说明

  1. Double-Check概念是多线程开发中经常出现的,如代码所示,我们进行了两次if(null == instance)检查,这样就可以保证线程安全了
  2. 第一次初始化时,即使是多线程,实例化代码也只执行一次。第一个线程进入同步块,实例化对象后,后面线程再次进入同步块时,判断if(null == instance),此时对象不为空,故第二次进入同步块实际什么也没做,之后返回实例化对象
  3. 之后再调用getInstance方法,在第一个if(null == instance)这里就跳过了同步块,直接返回实例化对象
  4. 综上,线程安全,延迟加载,效率较高

结论:实际开发中,推荐使用这种单例设计模式

静态内部类

代码:

package singleton.type7;

public class Singleton {

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

说明

  1. 静态内部类在Singleton类加载时不会立即实例化,在需要时才会实例化,这里就是调用getInstance()方法时,才会装载SingletonInstance类,从而完成Singleton的实例化。实现了懒加载
  2. 类的静态属性只会在第一次类加载的时候初始化,所以这里JVM帮我们保证了线程安全,在类初始化时,别的线程无法进入
  3. 综上,线程安全,利用静态内部类特点实现延迟加载,效率高

结论:推荐使用

枚举

代码:

package singleton.type8;

public enum Singleton {

	INSTANCE;
	
}

说明

  1. 借助JDK1.5添加的枚举来实现单例模式
  2. 不仅能避免多线程问题,还能防止反序列化重新创建新的对象

结论:推荐使用

JDK实例

在JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)

总结

  1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当要实例化一个单例类时,记得使用相应的方法获取对象,而不是使用new
  3. 单例模式使用场景:需要频繁进行创建和销毁对象、创建对象时耗时过多或耗费资源过多(即重量级对象),但又经常要用到的对象、工具类对象,频繁访问数据库或文件的对象(如数据源,session工厂等)
  4. 实际开发用类型:饿汉式(静态常量)、双重检查、静态内部类、枚举
posted @ 2019-09-24 16:37  淘气小饼干  阅读(181)  评论(0编辑  收藏  举报