Java单例模式的7种用法以及使用问题

Java 单例模式的几种用法

    • 1.概念
    • 2.饿汉式单例
    • 3.懒汉式单例
    • 4.双重检验锁单例
    • 5.反射破坏单例
    • 6.静态内部类单例
    • 7.序列化破坏单例
    • 8.枚举单例
    • 9.容器单例
    • 9.线程单例
    • 10.总结

1.概念

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

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

注意:

1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

2.饿汉式单例

	1.1 饿汉式单例
/**
 * 饿汉式单例
 * 
 * @author a_bo
 * @version 创建时间:2020年4月18日 下午4:09:42
 */
public class HungrySingleton {

	private static final HungrySingleton SINGLETON = new HungrySingleton();

	public static HungrySingleton getInstance() {
		return SINGLETON;
	}

	private HungrySingleton() {
	}

	public static void main(String[] args) {
		HungrySingleton s1 = HungrySingleton.getInstance();
		System.out.println("s1" + s1);
	}
}
	1.2 饿汉式静态代码块单例
/**
 * 饿汉式静态代码块单例
 * 
 * @author a_bo
 * @version 创建时间:2020年4月18日 下午4:15:18
 */
public class HungryStaticSingleton {

	private static final HungryStaticSingleton SINGLETON;
	static {
		SINGLETON = new HungryStaticSingleton();
	}

	public static HungryStaticSingleton getInstance() {
		return SINGLETON;
	}

	private HungryStaticSingleton() {
	}

	public static void main(String[] args) {
		HungryStaticSingleton s1 = HungryStaticSingleton.getInstance();
		System.out.println(s1);
	}
}

特点

1.饿汉式单例, 使用static修饰全局唯一, 在类加载的时候就会初始化, 线程安全 
2.如果有大批量的饿汉式单例, 系统初始化时会占用大量内存

3.懒汉式单例

	3.1 懒汉式单例
/**
 * 懒汉式单例
 * 
 * @author a_bo
 * @version 创建时间:2020年4月18日 下午5:10:46
 */
public class LazySingleton {

	private static LazySingleton singleton;

	private LazySingleton() {
	}

	private static LazySingleton getInstance() {
		if (singleton == null) {
			singleton = new LazySingleton();
		}
		return singleton;
	}

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

特点

	1.使用懒加载, 使用的时候才会去创建, 节省了初始化的内存空间
	2.在多线程的场景下会有几率创建两个不一样的单例对象, singleton == null 线程不安全

3.2 饿汉式同步锁单例

/**
 * 懒汉式单例
 * 
 * @author a_bo
 * @version 创建时间:2020年4月18日 下午5:10:46
 */
public class LazySingleton {

	private static LazySingleton singleton;

	private LazySingleton() {
	}

	private static synchronized LazySingleton getInstance() {
		if (singleton == null) {
			singleton = new LazySingleton();
		}
		return singleton;
	}

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

特点

	1.可以解决线程安全问题, 但是在多线程的场景下, 第一个线程进入同步方法后其余的线程会进行阻塞

4.双重检验锁单例

/**
 * 双重检验锁单例
 * 
 * @author a_bo
 * @version 创建时间:2020年4月18日 下午5:33:55
 */
public class DoubleCheckSingleton {

	private volatile static DoubleCheckSingleton singleton;

	private DoubleCheckSingleton() {
	}

	public static DoubleCheckSingleton getInstance() {
		if (singleton == null) {
			synchronized (DoubleCheckSingleton.class) {
				if (singleton == null) {
					singleton = new DoubleCheckSingleton();
				}
			}
		}
		return singleton;
	}

}	

特点

	1.相对于同步锁的单例, 双重检验锁的粒度更小, 而且DoubleCheckSingleton初始化后不会再进行
	同步的操作
	2.当多线程执行调用getInstance, 假设A线程先获取到锁, B线程阻塞, 在A线程初始化后通过volatile
	关键字将singleton 对象刷新到主内存, 保证了可见性, 然后释放锁, 此时B获取到锁, 判断 singleton 
	不为空, 就可以返回有值的对象

5.反射破坏单例

/**
 * 反射破坏单例创建多个实例
 * 
 * @author a_bo
 * @version 创建时间:2020年4月18日 下午5:57:26
 */
public class ReflectSingletonTest {

	public static void main(String[] args) throws Exception {
		// 通过静态方法获取单例
		DoubleCheckSingleton s1 = DoubleCheckSingleton.getInstance();
		// 通过反射用构造方法创建对象
		Class<?> clazz = DoubleCheckSingleton.class;
		Constructor<?> constructor = clazz.getDeclaredConstructor();
		constructor.setAccessible(true);
		DoubleCheckSingleton s2 = (DoubleCheckSingleton) constructor.newInstance();
		DoubleCheckSingleton s3 = (DoubleCheckSingleton) constructor.newInstance();
		System.out.println(s2 == s3);//false
		System.out.println(s1 == s2);//false
		System.out.println(s1 == s3);//false
	}
}

特点

	1.通过反射用构造方法创建出多个实例对象, 并且每个对象地址值不一致

解决办法

	可以对无参构造进行判断当前类的实例是否被创建
private volatile static DoubleCheckSingleton singleton;

	private DoubleCheckSingleton() {
		if (singleton != null) {
			throw new RuntimeException("不允许创建对象");
		}
	}

6.静态内部类单例

/**
 * 静态内部类单例
 * 
 * @author a_bo
 * @version 创建时间:2020年4月18日 下午6:07:14
 */
public class LazyInnerSingleton {

	private LazyInnerSingleton() {
		if (LazySingletoncreator.SINGLETON != null) {
			throw new RuntimeException("不允许创建对象");
		}
	}

	public static final LazyInnerSingleton getInstance() {
		return LazySingletoncreator.SINGLETON;
	}

	private static class LazySingletoncreator {
		private static final LazyInnerSingleton SINGLETON = new LazyInnerSingleton();
	}
}

特点

	1.内部类默认是不会被加载, 当使用时候才会被加载, SINGLETON 常量全局唯一只会创建一份, 保证了
	单例

7.序列化破坏单例

	1.将LazyInnerSingleton  实现序列化接口
	2.通过序列化再反序列化创建对象
/**
 * 序列化破坏单例测试
 * 
 * @author a_bo
 * @version 创建时间:2020年4月18日 下午6:25:59
 */
public class SerializableSingletonTest {

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

		// 创建输出流写入
		FileOutputStream os = new FileOutputStream("LazyInnerSingleton.java");
		ObjectOutputStream oos = new ObjectOutputStream(os);
		oos.writeObject(s1);
		oos.close();
		// 创建输入流读
		FileInputStream is = new FileInputStream("LazyInnerSingleton.java");
		ObjectInputStream ois = new ObjectInputStream(is);
		LazyInnerSingleton s2 = (LazyInnerSingleton) ois.readObject();
		oos.close();
		System.out.println(s1 == s2);//false
	}
}

特点

	1.通过序列化再发序列化会创建一个新对象

** 解决办法**

	1.在序列化的类中增加readResolve方法, ObjectInputStream 在底层会反射调用readResolve 
	感兴趣的可以看源码更一下
/**
 * 静态内部类单例
 * 
 * @author a_bo
 * @version 创建时间:2020年4月18日 下午6:07:14
 */
public class LazyInnerSingleton implements Serializable {
	private static final long serialVersionUID = 1530724903774028685L;
	/** 反序列化的时候返回的对象 */
	public Object readResolve() {
		return LazySingletoncreator.SINGLETON;
	}

	private LazyInnerSingleton() {
		if (LazySingletoncreator.SINGLETON != null) {
			throw new RuntimeException("不允许创建对象");
		}
	}

	public static final LazyInnerSingleton getInstance() {
		return LazySingletoncreator.SINGLETON;
	}

	private static class LazySingletoncreator {
		private static final LazyInnerSingleton SINGLETON = new LazyInnerSingleton();
	}
}

8.枚举单例

/**
 * 枚举单例
 * @author a_bo  
 * @date 2020年4月18日 下午9:53:03 
 */
public enum EnumSingleton {
	
	INSTANCE;
	private Object obj;
	
	public Object getData(){
		return obj;
	}
	public void setData(Object data){
		obj = data;
	}
	public static EnumSingleton getInstance(){
		return INSTANCE;
	}
	public static void main(String[] args) {
		EnumSingleton i = EnumSingleton.INSTANCE;
		i.setData(new Object());
		Object o1 = i.getData();
		System.out.println(o1);
	}
}	

特点

	1.枚举只能被类加载器加载一次,所以枚举可以保证唯一
	2.枚举不能被反射,反射会抛出异常
	3.枚举在反序列化时会根据类名和类对象找到唯一的一个枚举

9.容器单例

/**
 * 容器单例
 * @author a_bo  
 * @date 2020年4月18日 下午10:40:27 
 */
public class ContainerSingleton {
	
	private static Map<String, Object> containerMap = new ConcurrentHashMap<String,Object>();
	
	public static <T> T getInstance(Class<? extends T> clazz) throws Exception{
		String simpleName = clazz.getSimpleName();
		if (!containerMap.containsKey(simpleName)) {
			T t = clazz.newInstance();
			containerMap.put(simpleName, t);
			return t;
		}
		return (T) containerMap.get(simpleName);
	}
}

总结

	1.线程不安全
	2.适用创建大量对象

9.线程单例

/**
 * 线程单例
 * @author a_bo  
 * @date 2020年4月18日 下午10:57:32 
 */
public class ThreadLocalSingleton {
	
	private static final ThreadLocal<APP> instances = new ThreadLocal<APP>(){

		@Override
		protected APP initialValue() {
			return new APP();
		}
	};
	
	private ThreadLocalSingleton(){
	}
	
	public static APP getInstance(){
		return instances.get();
	}
	public static void main(String[] args) {
		APP a1 = ThreadLocalSingleton.getInstance();
		APP a2 = ThreadLocalSingleton.getInstance();
		System.out.println(a1==a2);//true
	}
}

特点

	1.只能保证在同一个线程下获取到的时单例的对象

10.总结

使用单例模式需要考虑一下几点

	1.线程安全
	2.延迟加载
	3.私有化构造
	4.防止反射破坏单例
	5.防止反序列化破坏单例
	6.针对业务场景选择最佳的单例模式

你可能感兴趣的:(Java基础,java,设计模式)