单例模式&有上限多例模式

单例模式(Singleton Pattern) 创建型模式,范畴:对象

定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式的通用类图

在这里插入图片描述
Singleton类称为单例类,通过使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化的
通用代码:

package com.lushunde.desingn.singleton;

/**
 * 通用模式-饿汉式
 * @author bellus
 *  */
public class Singleton {
	private static final Singleton singleton = new Singleton();

	// 限制产生多个对象
	private Singleton() {
	}

	// 通过该方法获得实例对象
	public static Singleton getSingleton() {
		return singleton;
	}

	// 类中其他方法,尽量是static
	public static void doSomething() {
	}
}

实现单例的几种形式

实现方式 优点 缺点 效率
饿汉式(通用形式) 线程安全,效率极高 缺少延时加载
懒汉式(方法同步) 线程安全,能延时加载 效率不高(每次加锁)
双重检测锁式(volatile) 线程安全,效率很高,能延时加载
静态内部类 线程安全,效率高,能延时加载
枚举单例 线程安全,效率高 ,能防止反序列化 缺少延时加载

饿汉式

天然线程安全,无懒加载,效率高


package com.lushunde.desingn.singleton.improve;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 通用模式-饿汉式,解决注意事项
 *  天然线程安全,无懒加载,效率高
 * @author bellus
 *
 */
public class Singleton implements Serializable {
	private static final long serialVersionUID = -7640246007296163797L;
	
	private static final Singleton singleton = new Singleton();

	// 限制产生多个对象
	private Singleton()  {
		//解决反射创建对象,直接抛异常
		if(singleton!=null){
			throw new RuntimeException("防止反射创建对象");
		}
	}
	
	// 通过该方法获得实例对象
	public static Singleton getSingleton() {
		return singleton;
	}

	// 类中其他方法,尽量是static
	public static void doSomething() {
	}
	
	//解决反序列化漏洞
	public Object readResolve() throws ObjectStreamException{
		return singleton;
	}
}

饿汉式(加锁)

每次获取都要加锁,影响效率

package com.lushunde.desingn.singleton.improve;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 通用模式-懒汉式,解决线程安全、反射漏洞、反序列化
 * 
 * @author bellus
 *
 */
public class Singleton2 implements Serializable {

	private static final long serialVersionUID = -7640246007296163797L;
	
	private static Singleton2 singleton = null;

	// 限制产生多个对象
	private Singleton2()  {
		//解决反射创建对象,直接抛异常
		if(singleton!=null){
			throw new RuntimeException("防止反射创建对象");
		}
	}

	// 通过该方法获得实例对象+ 加锁解决线程安全问题
	public static synchronized Singleton2  getSingleton() {
		if(singleton==null){
			singleton = new Singleton2();
		}
		return singleton;
	}

	// 类中其他方法,尽量是static
	public static void doSomething() {
	}
	
	//解决反序列化漏洞
	public Object readResolve() throws ObjectStreamException{
		return singleton;
	}
}

双重检查锁

DCL失效问题,通过volatile可以解决。

INSTANCE = new SingleTon();
这个步骤,其实在jvm里面的执行分为三步:
1.在堆内存开辟内存空间。
2.在堆内存中实例化SingleTon里面的各个参数。
3.把对象指向堆内存空间。
由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE 已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。
不过在JDK1.5之后,官方也发现了这个问题,故而具体化了volatile,即在JDK1.6及以后,只要定义为private volatile static SingleTon INSTANCE = null;就可解决DCL失效问题。volatile确保INSTANCE每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅。

package com.lushunde.desingn.singleton.improve;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 双重加锁,解决线程安全、效率、反射漏洞、反序列化
 * 
 * INSTANCE  = new SingleTon(); 
 * 这个步骤,其实在jvm里面的执行分为三步:
 *      1.在堆内存开辟内存空间。
 *      2.在堆内存中实例化SingleTon里面的各个参数。
 *      3.把对象指向堆内存空间。
 * 由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE 已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。
 * 不过在JDK1.5之后,官方也发现了这个问题,故而具体化了volatile,即在JDK1.6及以后,只要定义为private volatile static SingleTon  INSTANCE = null;就可解决DCL失效问题。volatile确保INSTANCE每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅。
 * @author bellus
 *
 */
public class Singleton3 implements Serializable {

	private static final long serialVersionUID = -7640246007296163797L;

	//volatile 解决DCL失效
	private static volatile Singleton3 singleton = null;

	// 限制产生多个对象
	private Singleton3() {
		// 解决反射创建对象,直接抛异常
		if (singleton != null) {
			throw new RuntimeException("防止反射创建对象");
		}
	}

	// 通过该方法获得实例对象+ 加锁解决线程安全问题,双重判断 提高效率
	public static  Singleton3 getSingleton() {
		if (singleton == null) {
			synchronized (Singleton3.class) {
				if (singleton == null) {
					singleton = new Singleton3();
				}
			}
		}
		return singleton;
	}

	// 类中其他方法,尽量是static
	public static void doSomething() {
	}

	// 解决反序列化漏洞
	public Object readResolve() throws ObjectStreamException {
		return singleton;
	}
}

静态内部类

通过类加载机制实现懒加载和单例

package com.lushunde.desingn.singleton.improve;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 静态内部类 
 * 外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存
 * 
 * @author bellus
 *
 */
public class Singleton4 implements Serializable {

	private static final long serialVersionUID = -7640246007296163797L;

	private Singleton4() {
		// 解决反射创建对象,直接抛异常
		if (SingletonHoler.INSTANCE != null) {
			throw new RuntimeException("防止反射创建对象");
		}
	}

	private static class SingletonHoler {
		private static Singleton4 INSTANCE = new Singleton4();
	}

	public static Singleton4 getSingleton() {
		return SingletonHoler.INSTANCE;
	}

	// 类中其他方法,尽量是static
	public static void doSomething() {
	}

	// 解决反序列化漏洞
	public Object readResolve() throws ObjectStreamException {
		return SingletonHoler.INSTANCE;
	}

}

枚举

天然的 线程安全、无反射漏洞,无序列化漏洞

package com.lushunde.desingn.singleton.improve;

import java.io.Serializable;

/**
 * 枚举 
 * 默认就  线程安全、无反射漏洞,无序列化漏洞
 * @author bellus
 *
 */
public enum Singleton5 implements Serializable {

	INSTANCE;

	// 类中其他方法,尽量是static
	public static void doSomething() {
	}

}

扩展:有上限的多例模式

含义:
产生固定数量对象的模式就叫做有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例,方便系统进行扩展,修正单例可能存在的性能问题,提供系统的响应速度。

常用场景

  • 读取文件,设置多个固定提高并发量,继而提升性能和响应速度。

  • 有上限的多例模式代码:
    MultitonSingleton 代码:

package com.lushunde.desingn.singleton.improve;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Random;

/**
 * 
 * 扩展:有上限的多例模式 **含义:**
 * 产生固定数量对象的模式就叫做有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例,方便系统进行扩展,
 * 修正单例可能存在的性能问题,提供系统的响应速度。 **常用场景**: 读取文件,设置多个固定提高并发量,继而提升性能和响应速度。
 * 
 * @author bellus
 *
 */
public class MultitonSingleton implements Serializable {

	private static final long serialVersionUID = -7640246007296163797L;

	// 定义最多能产生的实例的数量
	private static int maxNumOfMultiton = 2;

	// 定义一个列表,容纳所有实例
	private static ArrayList<MultitonSingleton> SingletonList = new ArrayList<MultitonSingleton>();
	// 当前实例的序列号
	private static int countNumOfMultiton = 0;

	// 产生所有对象
	static {
		for (int i = 0; i < maxNumOfMultiton; i++) {
			SingletonList.add(new MultitonSingleton());
		}
	}

	private MultitonSingleton() {
		// 解决反射创建对象,直接抛异常
		if (SingletonList.size() > maxNumOfMultiton) {
			throw new RuntimeException("创建数已达到最大数,防止反射创建对象");
		}
	}

	// 随机获得一个皇帝对象
	public static MultitonSingleton getInstance() {
		Random random = new Random();
		countNumOfMultiton = random.nextInt(maxNumOfMultiton);
		return SingletonList.get(countNumOfMultiton);
	}

	// 类中其他方法,尽量是static
	public static void doSomething() {

	}

	// 解决反序列化漏洞
	public Object readResolve() throws ObjectStreamException {
		return SingletonList.get(countNumOfMultiton);
	}

}

MultitonClient6 代码:

package com.lushunde.desingn.singleton.improve;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

/**
 * 有上限的多例模式 测试 正常调用 反射漏洞 反序列化漏洞
 * 
 * @author bellus
 *
 */

public class MultitonClient6 {

	public static void main(String[] args) {

		MultitonSingleton instance = MultitonSingleton.getInstance();

		// 多次调用,查看有多少对象
		test1();
		// 实现反射创建新对象
		testReflect(instance);
		// 通过反序列化
		testSerzi(instance);

	}

	private static void test1() {
		// 调用10次,查看对象
		MultitonSingleton instance = null;
		for (int i = 0; i < 10; i++) {
			instance = MultitonSingleton.getInstance();
			System.out.println(instance);
		}

	}

	private static void testReflect(MultitonSingleton instance) {
		try {

			// 实现反射创建新对象
			Class<?> classType = Singleton5.class;
			Class<?>[] cArg = new Class[0]; // 入参类型(空参)
			// 获取 构造方法 类
			Constructor<?> constructor = classType.getDeclaredConstructor(cArg);
			// 打开 私有方法开关
			constructor.setAccessible(true);
			// 创建新对象
			MultitonSingleton instance3 = (MultitonSingleton) constructor.newInstance();

			System.out.print("反射创建: ");
			if (instance == instance3) {
				System.out.println("同一个对象");
			} else {
				System.out.println("不同对象");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void testSerzi(MultitonSingleton instance) {
		// 通过反序列化获取对象和一中其中一个相同

		try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./singleton.txt"));
				ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./singleton.txt"))) {

			oos.writeObject(instance);
			oos.close();
			MultitonSingleton instance4 = (MultitonSingleton) ois.readObject();

			ois.close();
			System.out.println("反序列化获取对象:" + instance4);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

单例模式的优点

  • 由於单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  • 由於单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制)。
  • 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

单例模式的缺点

  • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。当然,在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。
  • 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
  • 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

单例模式的使用场景

  • 要求生成唯一序列号的环境
  • 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
  • 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。

具体场景

  • Spring中 bean对象创建,参数scope中有单例模式(默认,Spring容器可以管理这些Bean的生命期;如果采用非单例模式Prototype类型,则Bean初始化后的管理交由J2EE容器,Spring容器不再跟踪管理Bean的生命周期)
  • servlet中每个servlet实例(Application servlet)
  • Spring MVC 和 Struts1 框架中,控制器对象实用单例模式
  • 应用程序的日志应用一般都是用单例模式
  • 项目中读取配置文件对象
  • 操作系统的文件系统、任务管理器、回收站(windows)
  • 数据库的连接池(有上限的多例模式)

开发使用场景

  • 封装工具类(也可以直接static声明方式)
  • 计数器、生成唯一序列等

单例模式的注意事项

1. 单例模式的线程同步问题

饿汉式存在此问题,需要通过加锁synchronized实现线程安全

2. 考虑对象的clone复制实例

解决该问题的最好方法就是单例类不要实现Cloneable接口,此时clone方法不能复制

3. 通过反射创建对象

私有构造器加入非空判断抛异常,这样反射调用时就会抛异常

	private Singleton()  {
		//解决反射创建对象,直接抛异常
		if(singleton!=null){
			throw new RuntimeException("防止反射创建对象");
		}
	}

4. 通过反序列化创建对象

重写readresolve()方法,实现反序列化时直接返回已有的实例

	//解决反序列化漏洞
	public Object readResolve() throws ObjectStreamException{
		return singleton;
	}

反射和反序列化漏洞代码演示

singleton 代码:

package com.lushunde.desingn.singleton;

import java.io.Serializable;

/**
 * 通用模式-饿汉式
 * 
 * @author bellus
 *
 */
public class Singleton implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = -3273287152119518024L;
	private static final Singleton singleton = new Singleton();

	// 限制产生多个对象
	private Singleton() {
	}

	// 通过该方法获得实例对象
	public static Singleton getSingleton() {
		return singleton;
	}

	// 类中其他方法,尽量是static
	public static void doSomething() {
	}
}

Client 代码

package com.lushunde.desingn.singleton;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

/**
 * 测试 正常调用
 * 反射漏洞
 * 反序列化漏洞
 * @author bellus
 *
 */

public class Client {

	public static void main(String[] args) {

		Singleton singleton = Singleton.getSingleton();

		// 调用两次
		test1(singleton);
		// 实现反射创建新对象
		testReflect(singleton);
		// 通过反序列化
		testSerzi(singleton);

	}

	private static void test1(Singleton singleton) {
		// 调用两次,查看是否同一个对象
		Singleton singleton2 = Singleton.getSingleton();
		System.out.print("正常创建: ");
		if (singleton == singleton2) {
			System.out.println("同一个对象");
		} else {
			System.out.println("不同对象");
		}
	}

	private static void testReflect(Singleton singleton) {
		try {

			// 实现反射创建新对象
			Class<?> classType = Singleton.class;
			Class<?>[] cArg = new Class[0]; // 入参类型(空参)
			// 获取 构造方法 类
			Constructor<?> constructor = classType.getDeclaredConstructor(cArg);
			// 打开 私有方法开关
			constructor.setAccessible(true);
			// 创建新对象
			Singleton singleton3 = (Singleton) constructor.newInstance();

			System.out.print("反射创建: ");
			if (singleton == singleton3) {
				System.out.println("同一个对象");
			} else {
				System.out.println("不同对象");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void testSerzi(Singleton singleton) {
		// 通过反序列化

		try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./singleton.txt"));
				ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./singleton.txt"))) {

			oos.writeObject(singleton);
			oos.close();
			Singleton singleton4 = (Singleton) ois.readObject();

			ois.close();
			System.out.print("反序列化创建: ");
			if (singleton == singleton4) {
				System.out.println("同一个对象");
			} else {
				System.out.println("不同对象");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

结果:

正常创建: 同一个对象
反射创建: 不同对象
反序列化创建: 不同对象

反射和反序列化漏洞修复后代码演示

只需要修改单例代码:

package com.lushunde.desingn.singleton.improve;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 通用模式-饿汉式,解决注意事项
 * 
 * @author bellus
 *
 */
public class Singleton implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -7640246007296163797L;
	
	private static final Singleton singleton = new Singleton();

	// 限制产生多个对象
	private Singleton()  {
		//解决反射创建对象,直接抛异常
		if(singleton!=null){
			throw new RuntimeException("防止反射创建对象");
		}
	}

	// 通过该方法获得实例对象
	public static Singleton getSingleton() {
		return singleton;
	}

	// 类中其他方法,尽量是static
	public static void doSomething() {
	}
	
	
	//解决反序列化漏洞
	public Object readResolve() throws ObjectStreamException{
		return singleton;
	}

	
	
}

再次调用client结果如下:

正常创建: 同一个对象
java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.lushunde.desingn.singleton.improve.Client.testReflect(Client.java:54)
	at com.lushunde.desingn.singleton.improve.Client.main(Client.java:26)
Caused by: java.lang.RuntimeException: 防止反射创建对象
	at com.lushunde.desingn.singleton.improve.Singleton.<init>(Singleton.java:25)
	... 6 more
反序列化创建: 同一个对象
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章