软件设计模式-单例模式

117 阅读5分钟

创建者模式-单例模式

单例模式,是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它系统了一种创建对象的最佳方式。

这种模式设计到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种防范其唯一对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的结构

单例模式主要有以下角色:

  • 单例类:只能创建一个实例的类
  • 访问类:使用单例类

单例模式的实现

单例设计模式分为两种:

饿汉式:类加载就会导致该单实例对象被创建

懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

饿汉式

饿汉式1

私有构造方法、创建本类的静态成员变量并直接初始化、提供公共的访问方式

实例代码如下:

public class HungStyleOne {

    //创建本类静态成员变量
	private final static HungStyleOne hungStyleOne = new HungStyleOne();

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

	//提供公共的访问方式
	public static HungStyleOne getHungStyleOne(){
		return hungStyleOne;
	}

}

饿汉式2

私有构造方法、声明本类的静态成员变量、创建静态代码块,在代码块中赋值、提供公共的访问方式

实例代码如下:

public class HungStyleTwo {

	//声明本类静态成员变量
	private static HungStyleTwo hungStyleTwo;

	//静态代码块中赋值
	static {
		hungStyleTwo = new HungStyleTwo();
	}

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

	//提供公共访问方法
	public static HungStyleTwo getHungStyleTwo() {
		return hungStyleTwo;
	}
}

懒汉式

懒汉式1

私有构造方法、声明本类的静态成员变量、提供公共访问方式、在公共访问方式中判断本类静态变量是否为空,为空创建,不为空直接返回(此方法存在线程安全问题)

实例代码如下:

public class LazyStyleOne {
	
	//声明静态成员变量
	private static LazyStyleOne lazyStyleOne;
	
	//私有构造方法
	private LazyStyleOne(){
	}
	
	//公共访问方式
	public static LazyStyleOne getLazyStyleOne(){
		if (lazyStyleOne == null){
			lazyStyleOne = new LazyStyleOne();
		}
		return lazyStyleOne;
	}
}

懒汉式2

在懒汉式1的基础上,给对应的公共访问方式加上锁。实现线程安全

实例代码如下:

public class LazyStyleTwo {

	//声明静态成员变量
	private static LazyStyleTwo lazyStyleTwo;

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

	//公共访问方式
	public static synchronized LazyStyleTwo getLazyStyleTwo(){
		if (lazyStyleTwo == null){
			lazyStyleTwo = new LazyStyleTwo();
		}
		return lazyStyleTwo;
	}
}

懒汉式3

在懒汉式2的基础上,提供双重检查锁,在判断到本类静态变量为空时,获得锁,在锁内部也进行一次为空判断

双重检查锁在锁粒度上相较于懒汉式2锁粒度更小,锁内部需要判断的原因为:如果有其余线程在第一个if条件中等待获取锁,如果在锁内部不进行判断,则会导致单例模式被破坏

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,但是懒汉式3还是无法解决多线程存在的指令重排序问题,可能会存在空指针,此时就需要使用volatile关键字修饰本类静态实例对象

实例代码如下:

public class LazyStyleThree {
	//声明静态成员变量
	private static LazyStyleThree lazyStyleThree;

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

	//公共访问方式
	public static LazyStyleThree getLazyStyleThree(){
		if (lazyStyleThree == null){
			synchronized (LazyStyleThree.class){
				if (lazyStyleThree != null){
					lazyStyleThree = new LazyStyleThree();
				}
			}
		}
		return lazyStyleThree;
	}
}

懒汉式4(静态内部类)

静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,不会加载静态内部类,只有内部类的属性、方法被调用时才会被加载,并初始化其静态属性,静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序

懒汉式4是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加载任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费

实例代码如下:

public class LazyStyleFour {

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

	//静态内部类
	private static class SingleTon{
		private static final LazyStyleFour lazyStyleFour = new LazyStyleFour();
	}

	//公共访问方法
	public static LazyStyleFour getLazyStyleFour(){
		return SingleTon.lazyStyleFour;
	}

}

枚举方式

枚举类实现单例模式是极力推荐的方式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分利用了枚举的这个特性来实现单例模式,枚举类型的写法非常简单,而且枚举类型是所用到哪里实现中唯一一种不会被破坏的单例实现模式。

实例代码如下:

public enum EnumStyle {

	EnumStyle;
	//方法逻辑代码
	public void singleTon(){
		return;
	}
}

破坏单例模式

使上面的单例类可以创建多个对象,枚举方式除外,有两种方式,分别是序列化和反射

序列化/反序列化

public class TestOne {
	//获取运行目录
	private static String property = System.getProperty("user.dir") + "\\src\\main\\resources\\file\\hungStyleOne.txt";

	public static void main(String[] args) throws Exception {
		writeObjectToFile();
		HungStyleOne hungStyleOne = readObjectFromFile();
		System.out.println(hungStyleOne == HungStyleOne.getHungStyleOne());
	}

	public static void writeObjectToFile() throws Exception {
		//1.获取单例模式对象
		HungStyleOne hungStyleOne = HungStyleOne.getHungStyleOne();
		//2.创建对象输出流对象
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(property));
		//3.写对象
		oos.writeObject(hungStyleOne);
		//4.释放资源
		oos.close();
	}

	public static HungStyleOne readObjectFromFile() throws Exception {
		//1.创建对象输入流文件
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(property));
		//2.读取对象
		HungStyleOne hungStyleOne = (HungStyleOne) ois.readObject();
		//3.关闭流
		ois.close();
		//4.返回对象
		return hungStyleOne;
	}
}

反射

public class TestTwo {
	public static void main(String[] args) throws Exception {
		//1.获取字节码文件
		Class clazz = HungStyleOne.class;
		//2.获取无参构造方法对象
		Constructor cons = clazz.getDeclaredConstructor();
		//3.取消访问检查
		cons.setAccessible(true);
		//4.创建对象
		HungStyleOne s1 = (HungStyleOne) cons.newInstance();
		HungStyleOne s2 = (HungStyleOne) cons.newInstance();
		System.out.println(s1 == s2);
		System.out.println(s1 == HungStyleOne.getHungStyleOne());
		System.out.println(s2 == HungStyleOne.getHungStyleOne());
	}
}

解决问题

序列化、返序列化

在单例模式类中添加readResolve()方法,在反序列化时被调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象

//解决反序列化问题
public Object readResolve(){
	return HungStyleOne.hungStyleOne;
}

反射方式破解单例

//解决反射方式破解单例的解决方法
private HungStyleOne() throws Exception {
	if (HungStyleOne.hungStyleOne != null){
		throw new Exception();
	}
}

JDK中的单例模式

Runtime类(饿汉式)