JAVA的23种设计模式之单例模式

一、饿汉式单例模式

package cn.fllday.single;
/**
 * 饿汉式 单例模式
 * @author gssznb
 */
public class HungrySinglePatterns {
	
	// 类进行初始化的时候,就立即加载这个对象。没有延时加载的优势。加载类时,线程是安全的。
	private static HungrySinglePatterns instance = new HungrySinglePatterns();
	
	private HungrySinglePatterns() {	}
	
	// 方法没有同步,调用率高
	public static HungrySinglePatterns getInstance() {
		return instance;
	}
}

二、懒汉式单例模式

package cn.fllday.single;

/**
 * 懒汉式单例模式
 * @author gssznb
 *
 */
public class LazyLoadSinglePatterns {
	private static LazyLoadSinglePatterns instance;
	/**
	 * 私有化构造器
	 */
	private LazyLoadSinglePatterns() {	}
	
	/**
	 * 因为是延时加载所以有可能出现线程同步的问题。所以要加上 同步块。
	 * 如果A线程执行 方法。 instance为null的时候, B线程又要执行方法,那么这个时候,两个线程都判断instance为null
	 * 就会创建出两个对象。 所以要加上synchronized。 导致调用效率不高。
	 * @return
	 */
	public static synchronized LazyLoadSinglePatterns getInstance() {
		if (instance == null) {
			return new LazyLoadSinglePatterns();
		}
		return instance;
	}
}

双重检测锁实现单例模式

package cn.fllday.single;

/**
 * 双重检测锁实现单例模式
 * 懒加载,调用率高。没有同步问题。但是因为jvm底层问题,容易出现问题,不推荐使用
 * @author gssznb
 *
 */
public class DoubuCheckLockedSinglePatterns {
	
	private static DoubuCheckLockedSinglePatterns instance;
	
	private DoubuCheckLockedSinglePatterns() {
		// TODO Auto-generated constructor stub
	}
	
	public static DoubuCheckLockedSinglePatterns getInstance() {
		if (instance == null) {
			DoubuCheckLockedSinglePatterns dcl;
			synchronized (DoubuCheckLockedSinglePatterns.class) {
				dcl = instance;
				if (dcl == null) {
					synchronized (DoubuCheckLockedSinglePatterns.class) {
						if (dcl == null) {
							dcl = new DoubuCheckLockedSinglePatterns();
						}
					}
					instance = dcl;
				}
			}
		}
		return instance;
	}
}

静态内部类实现单例模式

package cn.fllday.single;

/**
 * 静态内部类,实现懒加载单例模式
 * 线程安全,懒加载,并且实现了延时加载。调用效率高
 * @author gssznb
 *
 */
public class StaticInteriorSinglePatterns {
	
	private static class SingletonClassInstance{
		private static final StaticInteriorSinglePatterns instance = new StaticInteriorSinglePatterns();
	}
	
	private StaticInteriorSinglePatterns() {	}
	
	public static StaticInteriorSinglePatterns getInstance() {
		return StaticInteriorSinglePatterns.SingletonClassInstance.instance;
	}	
}

枚举实现单例模式

package cn.fllday.single;

/**
 * 枚举测试实现单例模式
 * @author gssznb
 *
 */
public enum EnumSinglePatterns {
	// 枚举元素本身就是单例对象
	/**
	 * 避免了反射和反序列化的漏洞,调用效率比较高。但是没有懒加载的效果
	 */
	INSTANCE;
	
	// 添加自己需要的操作。
	public void singletonOperation() {
	}
}

验证单例模式创建出来的对象是否唯一

package cn.fllday.single;

public class Client {
	public static void main(String[] args) {
		HungrySinglePatterns instance1 = HungrySinglePatterns.getInstance();
		HungrySinglePatterns instance2 = HungrySinglePatterns.getInstance();
		HungrySinglePatterns instance3 = HungrySinglePatterns.getInstance();
		System.out.println(instance1);
		System.out.println(instance2);
		System.out.println(instance3);
		
		EnumSinglePatterns instance4 = EnumSinglePatterns.INSTANCE;
		EnumSinglePatterns instance5 = EnumSinglePatterns.INSTANCE;
		System.out.println(instance4);
		System.out.println(instance5);
	}
}

打印结果

cn.fllday.single.HungrySinglePatterns@15db9742
cn.fllday.single.HungrySinglePatterns@15db9742
cn.fllday.single.HungrySinglePatterns@15db9742
INSTANCE
INSTANCE

通过反射机制来创建多个单例对象

package cn.fllday.single;

import java.lang.reflect.Constructor;

/**
 * 如果通过反射来构建单例的多个对象
 * @author gssznb
 *
 */
public class Client2 {
	public static void main(String[] args) throws Exception {
		HungrySinglePatterns2 instance1 = HungrySinglePatterns2.getInstance();
		HungrySinglePatterns2 instance2 = HungrySinglePatterns2.getInstance();
		System.out.println(instance1);
		System.out.println(instance2);
		
		
		Class clazz =  (Class) Class.forName("cn.fllday.single.HungrySinglePatterns2");
		// 获取该类的构造方法
		Constructor c = clazz.getDeclaredConstructor(null);
		// 设置可以跳过权限访问检测
		c.setAccessible(true);
		// 通过newInstance()方法 创建该类的对象
		HungrySinglePatterns2 instance3 = c.newInstance();
		HungrySinglePatterns2 instance4 = c.newInstance();
		System.out.println(instance3);
		System.out.println(instance4);
	}
}

解决如果通过反射来创建多个单例对象

反射来创建多个对象是通过 反射来获取到 私有化的构造器来创建的对象。我们只需要在构造器中做一个判断就可以了。

解决方案:

	/**
	 * 解决通过反射机制来创建多个单例对象
	 */
	private HungrySinglePatterns2() {
		// 如果是第一次创建对象 就可以成功创建
		// 如果第二次创建那么 instance对象就不为空,我们抛出一个异常
		if (instance != null) {
			throw new RuntimeException();
		}
	}

通过序列化来创建多个单例对象

package cn.fllday.single;

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

/**
 * 如何通过序列化 来创建多个单例对象
 * @author gssznb
 *
 */
public class Client3 {
	@SuppressWarnings("resource")
	public static void main(String[] args) throws Exception {
		HungrySinglePatterns2 instance1 = HungrySinglePatterns2.getInstance();
		HungrySinglePatterns2 instance2 = HungrySinglePatterns2.getInstance();
		System.out.println(instance1);
		System.out.println(instance2);
		
		// 通过序列化来创建多个对象
		FileOutputStream fos = new FileOutputStream("Z:/a.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(instance1);
		
		// 通过反序列化来还原对象
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("z:/a.txt"));
		HungrySinglePatterns2 instance3 = (HungrySinglePatterns2)ois.readObject();
		System.out.println(instance3);
	}
}

如果解决序列化方式创建多个对象

在单例类中, 写一个readReslove() 方法,则直接返回此方法指定的对象,而不需要单独再次创建对象


	// 反序列化时,如果定义了readReslove()则直接返回此方法指定的对象,而不需要单独再次创建对象。
	private Object readResolve() throws ObjectStreamException {
		return instance;
	}

结论

饿汉式: 线程安全,调用率高。但是不能延时加载
懒汉式: 线程安全,调用率不高。可以延时加载。
双重检查锁: 由于JVM底层内部模型原因,偶尔会出现问题。不建议使用
静态内部类:线程安全,调用率高。延时加载
枚举式:线程安全,调用率高。不能延时加载。但是有天然的防止反射和序列化进行创建多个对象。

如何选择哪一种单例模式

单例对象: 占用资源少 ,不需要延时加载。

  • 枚举 大于 饿汉式

单例对象: 占用资源多,需要延时加载。

  • 静态内部类好于 懒汉式

以上就是单例模式的学习。

你可能感兴趣的:(设计模式,Java,单例)