浅谈GOF设计模式之单例模式(三)

核心作用及应用场景

核心作用

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

常见应用场景

1.Windows的Task Manager(任务管理器)就是很典型的单例模式
2.windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例
3.网站的计数器(例如实时在线人数等),一般也是采用单例模式实现,否则难以同步。
4.应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作
,否则内容不好追加。
5.数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
6.操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
7.servlet 中的 Application 也是单例的典型应用
8.在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
9.在servlet编程中,每个Servlet也是单例
10.在spring MVC框架/struts1框架中,控制器对象也是单例

单例模式的优点

1.由于单例模式只生成一个实例,减少了系统性能开销,
当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
– 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理

常见的五种单例模式实现方式:

推荐静态内部类(效率高仅次于饿汉式),和枚举方式(枚举还能防破解)得方式
主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
– 其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举单例(线程安全,调用效率高,不能延时加载)

单例模式的选用:

_单例对象 占用 资源 少,不需要 延时加载:
• 枚举式 好于 饿汉式
– 单例对象 占用 资源 大,需要 延时加载:
• 静态内部类式 好于 懒汉式

五种单例的实现以及优缺点

饿汉模式(单例对象立即加载)

1.饿汉模式(单例对象立即加载),线程安全的(创建出来之家在一次),调用效率高,不能掩饰加载,缺点是不调用的时候也会加载

2.在额韩式单例中,static变量会在类装载时初始化,此时也不会设计多个线程对象访问该对象的问题,虚拟机保证只会蝗灾一次该类,肯定不会发生并发访问的问题,可以胜率 synchronized (同步锁)关键字

3.如果只是加载本类,而事实调用getInstance() ,甚至永远没有调用,就会造成资源浪费

/**
 * 
 * @author 季久亮<jijiuliang@sohu.com> 2019年4月9日
 * 
 * 
 *         饿汉模式
 */
 //饿汉式(静态变量实现)
public class Single {
	//类初始化时,立即加载这个对象(一次),这个类时,天然的线程安全
	//静态成员加载 天然的是线程安全的,效率高
	private static /*final*/ Single instance = new Single(); //提供私有化的静态属性存放实例  -- 单例

	//方法没有同步,调用效率高
	//只能通过公共的静态的方法获取单例
	public static Single getInstance() { 
		return instance;
	}

	// 将构造器设置为private禁止通过new进行实例化
	private Single() { 

	}
}

测试



public class SingletonTest01 {

	public static void main(String[] args) {
		//测试
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());
	}

}

//饿汉式(变种 静态块实现) 
class Singleton {
	
	//1. 构造器私有化, 外部不能new
	private Singleton() {
		
	}
	
	//2.本类内部创建对象实例
	private static Singleton instance;

	static { // 在静态代码块中,创建单例对象
		instance = new Singleton();
	}	

	//3. 提供一个公有的静态方法,返回实例对象
	public static Singleton getInstance() {
		return instance;
	}
	
}

输出

true
instance.hashCode=366712642
instance2.hashCode=366712642

懒汉式单例(单例对象立即加载)

lazy load:延时加载,懒加载!真正用的时候才加载
资源利用率太高了,但是每次调用getInstance()方法都要同步,并发效率低,调用效率低
== 第一种线程不安全的==

public class SingletonTest04 {

	public static void main(String[] args) {
		System.out.println("懒汉式2 , 线程安全~");
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());
	}

}

//线程不安全
//懒汉式
class Singleton {
	//成员对象
	private static Singleton instance;
	
	//初始化私有化构造器,只能内部创建
	private Singleton() {}
	
	//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
	//即懒汉式
	public static Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

//线程安全 效率低
// 懒汉式(线程安全,同步方法)
class Singleton {
	private static Singleton instance;
	
	private Singleton() {}
	
	//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
	//即懒汉式 调用效率低 synchronized 同步锁
	public static synchronized Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

静态内部类(线程安全,调用效率高,可以延时加载)懒加载

==lazy load:延时加载,外部类没有static属性不会像饿汉式一样立即加载对象=
只有真正调用getInstance()才会加载静态内部类,加载类时是线程安全的,instance是static final 类型的,保证看了内崔中只有一个实例存在,而且只能复制一次,所以是线程安全的
兼并了并发高效调用个延时加载的优势
//调用的时候不要加同步synchronized,类加载的过程是天然的线程安全的不需要多此一举

public class SingletonTest07 {

	public static void main(String[] args) {
		System.out.println("使用静态内部类完成单例模式");
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());
		
	}

}

// 静态内部类完成, 推荐使用
class Singleton {
	private static volatile Singleton instance;
	
	//构造器私有化
	private Singleton() {}
	
	//写一个静态内部类,该类中有一个静态属性 Singleton
	private static class SingletonInstance {
		private static final Singleton INSTANCE = new Singleton(); 
	}
	
	//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
	//调用的时候不要加同步synchronized,类加载的过程是天然的线程安全的不需要多此一举
	public static /*synchronized*/ Singleton getInstance() {
		
		return SingletonInstance.INSTANCE;
	}
}

双重检查锁模式()

错误写法

public class SingletonTest06 {

	public static void main(String[] args) {
		System.out.println("双重检查");
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());
		
	}

}

// 懒汉式(线程安全,同步方法)
class Singleton {
	private static volatile Singleton instance;
	
	private Singleton() {}
	
	//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
	//因为 synchronized  还是加载方法 导致每次调用依然是同步,效率跟懒汉式一样低下
	//网上有很多以为这是双重检查锁的写法实际上这是错误的,根本没有意义,与想解决的问题根本就是冲突的,只是双重检查而已
	public static synchronized Singleton getInstance() {
		if(instance == null) {
			synchronized (Singleton.class) {
				if(instance == null) {
					instance = new Singleton();
				}
			}
			
		}
		return instance;
	}
}

正确写法
由于编译器优化问题和JVM底层内部模型缺陷,导致偶尔会出问题,不建议使用
实现原理将同步内容下方if内部,不用每次获取对象时同步,只有第一次创建时同步,提高了执行效率

	//正确的写法
	//由于编译器优化问题和JVM底层内部模型缺陷,导致偶尔会出问题,不建议使用
	//实现原理将同步内容下方if内部,不用每次获取对象时同步,只有第一次创建时同步,提高了执行效率
	public static  Singleton getInstanceTrue() {
		if(instance == null) {
			Singleton sc;
			synchronized (Singleton.class){
				sc=instance;
				if(sc == null) {
					synchronized (Singleton.class) {
						if (sc == null) {
							sc = new Singleton();
						}
					}
				}
			}
			instance = sc;
		}
		return instance;
	}

枚举方式(天然的单例,实现简单)

优点:实现简单
枚举本身就是单例模式,由jvm从根本上调用提供保障!避免通过反射和反序列化的漏洞
缺点:无延时加载

public class SingletonTest08 {
	public static void main(String[] args) {
		Singleton instance = Singleton.INSTANCE;
		Singleton instance2 = Singleton.INSTANCE;
		System.out.println(instance == instance2);
		
		System.out.println(instance.hashCode());
		System.out.println(instance2.hashCode());
		
		instance.sayOK();
	}
}
//使用枚举,可以实现单例, 推荐
//因为枚举在反编译后,每个枚举值都是抽象枚举类类型的 static final 的变量,所以天然的线程安全
enum Singleton {
	INSTANCE; //属性
	public void sayOK() {
		System.out.println("ok~");
	}
}

测试时间

饿汉式 22ms
懒汉式 636ms
静态内部类式 28ms
枚举式 32ms
双重检查锁式 65ms

破解单例模式的几个问题

问题:
反射可以破解上面几种(不包含枚举式)实现方式!(可以在构造方法中手动抛出异常控制)
反序列化可以破解上面几种((不包含枚举式))实现方式!
• 可以通过定义readResolve()防止获得不同对象。
– 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。

public class SingletonDemo01 implements Serializable {
private static SingletonDemo01 s;
private SingletonDemo01() throws Exception{
if(s!=null){
throw new Exception("只能创建一个对象");
//通过手动抛出异常,避免通过反射创建多个单例对象!
}
} //私有化构造器
public static synchronized SingletonDemo01 getInstance() throws Exception{
if(s==null){
s = new SingletonDemo01();
}
return s;
}
//反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
private Object readResolve() throws ObjectStreamException {
return s;
}
}

验证反射和反序列化

public class Client2 {
	
	public static void main(String[] args) throws Exception {
		SingletonDemo6 s1 = SingletonDemo6.getInstance();
		SingletonDemo6 s2 = SingletonDemo6.getInstance();
		
		System.out.println(s1);
		System.out.println(s2);
		
		//通过反射的方式直接调用私有构造器
//		Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("com.bjsxt.singleton.SingletonDemo6");
//		Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null);
//		c.setAccessible(true);
//		SingletonDemo6  s3 = c.newInstance();
//		SingletonDemo6  s4 = c.newInstance();
//		System.out.println(s3);
//		System.out.println(s4);
		
		//通过反序列化的方式构造多个对象 
		FileOutputStream fos = new FileOutputStream("d:/a.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(s1);
		oos.close();
		fos.close();
		
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
		SingletonDemo6 s3 =  (SingletonDemo6) ois.readObject();
		System.out.println(s3);
		
		
	}
}

多线程场景下测试各个单例的效率

public class Client3 {
	
	public static void main(String[] args) throws Exception {
		
		long start = System.currentTimeMillis();
		int threadNum = 10;
		final CountDownLatch  countDownLatch = new CountDownLatch(threadNum);
		
		for(int i=0;i<threadNum;i++){
			new Thread(new Runnable() {
				@Override
				public void run() {
					
					for(int i=0;i<1000000;i++){
//						Object o = SingletonDemo4.getInstance();
						Object o = SingletonDemo5.INSTANCE;
					}
					
					countDownLatch.countDown();
				}
			}).start();
		}
		
		countDownLatch.await();	//main线程阻塞,直到计数器变为0,才会继续往下执行!
		
		long end = System.currentTimeMillis();
		System.out.println("总耗时:"+(end-start));
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值