GOF23--Java设计模式--单例模式面试笔试内容(包含饿汉式、懒汉式、双重检查锁懒汉式、静态内部类式、枚举式)

Java设计模式 つ 单例模式

定义:确保一个类只有一个实例,并提供全局访问。
实现:私有化构造器、一个私有静态变量、一个公有静态方法。

一、需要注意的点:
要点 / 名称饿汉式普通懒汉式同步懒汉式双重检查锁懒汉式静态内部类枚举
要有懒加载
线程安全
调用效率高
防反射漏洞
防反序列化漏洞
二、单例模式实现方法
1、饿汉式
实现要点:
  1. 初始化静态对象;
  2. 私有化构造器;
  3. 对外提供静态获取方法。
/**
 * 单例模式 - 饿汉式
 */
public class Singleton1{
	//类初始化时,立即加载对象!类加载时,是天然线程安全的,但没有延时加载优势
	private static Singleton1 instance= new Singleton1();
	//私有化构造器
	private Singleton1(){}
	//方法没有同步,调用效率高
	public static Singleton1 getIntance(){
		return instance;
	}
}
2、懒汉式
实现要点:
  1. 初始化静态对象为null;
  2. 私有化构造器;
  3. 对外提供静态方法获取对象,该方法判断一次对象为空就创建对象。
/**
 * 单例模式 - 普通懒汉式
 */
public class Singleton2{
	//类初始化时,不初始化这个对象(延时加载,真正用的是否再创建)
	private static Singleton2 instance = null;
	//私有化构造器
	private Singleton2(){}
	//方法没有同步,调用效率高,只适用于单线程
	public static Singleton2 getInstance(){
		if(instance==null){
			instance = new Singleton2();
		}
		return instance;
	}
}
3、同步懒汉式
实现要点:
  1. 初始化静态对象为null;
  2. 私有化构造器;
  3. 对外提供静态同步方法获取对象,该方法判断一次对象为空就创建对象。
/**
 * 单例模式 - 同步懒汉式
 */
public class Singleton3{
	//类初始化时,不初始化这个对象(延时加载,真正用的是否再创建)
	private static Singleton3 instance = null;
	//私有化构造器
	private Singleton3(){}
	//方法同步,调用效率低
	public static synchronized Singleton3 getInstance(){
		if(instance==null){
			instance = new Singleton3();
		}
		return instance;
	}
}
4、双重检查锁懒汉式
volatile说明:
instance=new Singleton4();
//这句话创建了一个对象,他可以分解成为如下3行代码:
	memory = allocate();   // 1.分配对象的内存空间
	ctorInstance(memory);  // 2.初始化对象
	sInstance = memory;    // 3.设置sInstance指向刚分配的内存地址
/*
此时对象还没有被初始化上述伪代码中的2和3之间可能会发生重排序,
造成先指向内存地址,但是没有初始化,第二个线程进来不是null,
但是没有初始化,会发生错误。
解决方案:使用volatile定义变量,重排序在多线程环境中将会被禁止
注意:Java1.4及更早的版本,volatile会失效。
*/
实现要点:
  1. 初始化volatile+静态对象为null;
  2. 私有化构造器;
  3. 对外提供静态方法取获对象,该方法两次判断对象是否为空,第一次为空获取锁,第二为空创建对象
/**
 * 单例模式 - 双重检查锁懒汉式
 */
public class Singleton4{
	//类初始化时,不初始化这个对象(延时加载,真正用的是否再创建)
	private static volatile Singleton4 instance = null;
	//私有化构造器
	private Singleton4(){}
	public static Singleton4 getInstance(){
		//创建对象后不需要获得锁,直接返回对象
		if(instance==null){
			synchronized(Singleton4.class){
				if(instance==null){
					instance = new Singleton4();
				}
			}
		}
		return instance;
	}
}
5、静态内部类式
实现要点:
  1. 私有静态内部类;
  2. 静态内部类内部初始化静态对象;
  3. 私有化构造器,对外提供静态方法获取对象。
/**
 * 单例模式 - 静态内部类实现方式
 */
public class Singleton5{
	//静态内部类
	private static class SingletonClassInstance{
		private static final Singleton5 instance = new Singleton5();
	}
	//私有化构造器
	private Singleton5(){}
	//线程安全,调用效率高,并且实现了延时加载
	public static Singleton5 getInstance(){
		return SingletonClassInstance.instance;
	}
}
6、枚举式
实现要点:
  1. 直接定义枚举元素
/**
 * 单例模式 - 枚举方式(没有延时加载,天然避免反序列化和反射漏洞)
 */
public enum Singleton6{
	INSTANCE;
}
三、什么是反射漏洞

答:可以通过反射的方式直接调用私有构造器创建对象,破坏单例模式唯一性。

反射示例演示:
  1. 通过Class.forName(“包名.类名”)获得字节码文件;
  2. 通过getDeclaredContructor(null)获得字节码文件无参构造器;
  3. 通过setAccessible(ture)暴力访问私有构造器;
  4. 通过NewInstance()创建多个不同的对象。
//反射创建对象
public class Client {
	public static void main(String[] args) throws Exception{
		//通过反射的方式直接调用私有构造器
		Class<Singleton4> clazz = (Class<Singleton4>) Class.forName("com.Singleton.Singleton4");//获得class字节码文件
		Constructor<Singleton4> c = clazz.getDeclaredConstructor(null);//获得无参构造器
		c.setAccessible(true);//暴力访问
		Singleton4 s1 = c.newInstance();
		Singleton4 s2 = c.newInstance();
		System.out.println(s1==s2);//false
	}
}
四、什么是反序列化漏洞

答:可通过反序列化的方式构造多个不同对象,破坏单例模式的唯一性。

第1步、序列化示例演示:
  1. 序列化前提对象已经实现Serializable接口
  2. 创建文件输出流FileOutputStream(“目标路径”);
  3. 用对象输出流装饰ObjectOutputStream(2);
  4. 写出序列化对象writeObject(s);
  5. 关闭文件流、对象流
第2步、反序列化示例演示:
  1. 创建文件输入流FileInputStream(“目标路径”);
  2. 用对象输出流装饰ObjectInputStream(1);
  3. 读取序列化对象readObject();
  4. 关闭文件流、对象流。
public class Client {
	public static void main(String[] args) throws Exception{
		//1、序列化
		Singleton4 s1 = new Singleton4();
		FileOutputStream fos = new FileOutputStream("C:/a.obj");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(s1);
		oos.close();
		fos.close();
		//2、反序列化得到对象
		FileInputStream fis = new FileInputStream("C:/a.obj")
		ObjectInputStream ois = new ObjectInputStream(fis);
		Singleton4 s2 = (Singleton4) ois.readObject();
		System.out.println(s1==s2);//false
		fis.close();
		ois.close();
	}
}
五、如何避免反射漏洞

答:私有化构造器中增加判断,如果对象不为空,抛出异常

private Singleton(){
	if(instance!=null){
		throw new RuntimeException();
	}
}
六、如何避免反序列化漏洞

答:类中定义私有readResole()方法防止获取不同对象

private Object readResolve() {
	return instance;
}
七、多线程测试5种线程安全单例模式,创建对象的效率(相对)

答:

public class Client {
	public static void main(String[] args) throws Exception{
		long start = System.currentTimeMillis();
		int threadNum=100;
		//CountDownLatch:同步辅助类,在完成一组正在其它线程中执行的操作前,它允许一个或多个线程一直等待。
		final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
		for (int j = 0; j < threadNum; j++) {
			new Thread(new Runnable() {
				public void run() {
					for (int k = 0; k < 1000000; k++) {
						Object o = SingletonDemo1.getInstance();
					}
					//countDown(),当前线程调用此方法,则计数减一(建议放在finally里执行)
					countDownLatch.countDown();
				}
			}).start();
		}
		//await(),调用此方法会一直阻塞当前线程,知道计时器的值为0
		countDownLatch.await();//main线程阻塞,直到计数器变为0,继续往下执行
		long end = System.currentTimeMillis();
		System.out.println("饿汉式总耗时:"+(end-start));
	}
}
//结果1	
	饿汉式总耗时:23
	懒汉式总耗时:912
	双重锁总耗时:51
	静态类总耗时:81
	枚举式总耗时:53
//结果2
	饿汉式总耗时:60
	懒汉式总耗时:765
	双重锁总耗时:94
	静态类总耗时:71
	枚举式总耗时:54
八、选用原则

答:
1、单例对象,占用资源少,不要延时加载时
枚举式比饿汉式好。

2、单例对象,占用资源大,需要延时加载
静态内部类式比懒汉式好。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值