GOF23之单例模式

核心作用:

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

就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。在Java,一般常用在工具类的实现或创建对象需要消耗资源。

常见应用场景:

1、任务管理器
2、回收站
3、网站计数器
4、数据库连接池

优点:

1、单例模式只能产生一个实例,减少了系统性能的开销,当一个对象的产生需要比较多的资源时,如“读取配置,产生其他依赖对象”可以通过在应用启动时产生一个单例对象,然后永久驻留内存
2、单例模式可以在系统设置一个全局访问点,优化环境共享资源访问。

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

饿汉式(线程安全、调用效率高、不支持延时加载)
懒汉式(线程安全、调用效率低、支持延时加载)
双重检查锁式 (由于JVM底层内部模型原因,偶尔出现问题,不建议使用,但可用voletile修饰实例变量解决问题)
静态内部类(线程安全、调用效率搞、支持延时加载)
枚举单例(线程安全、调用效率高、不支持延时加载)

一、饿汉式实现(单例对象立即加载)

package com.hezeu.singleton;
/**
*@ClassnameSingletonDemo01
*@Description 测试饿汉式单例模式
*@Date2020/2/19下午03:48
*@Createdby朱进博 [email protected]
*/
public class SingletonDemo01{

	private static SingletonDemo01instance = new SingletonDemo01();

	private SingletonDemo01(){}

	publicstatic SingletonDemo01 getInstance(){
		return instance;
	}
}

优点:饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会设计多个线程对象访问该对象的问题,虚拟机保证只会加再一次类,不会发生并发访问。 问题:如果只是加载类,不调用getInstance()方法,会造成资源浪费

二、懒汉式实现(单例对象延迟加载)

package com.hezeu.singleton;
/**
*@ClassnameSingletonDemo02
*@Description懒汉式单例模式
*@Date2020/2/19下午03:48
*@Createdby朱进博
*/
public class SingletonDemo02{

	private static SingletonDemo02instance;

	private SingletonDemo02(){}

	public static synchronized SingletonDemo02 getInstance(){
		if(instance==null){
			instance=newSingletonDemo02();
		}
		returninstance;
	}
}

优点:Lazy load! 延迟加载,懒加载,用时再加载
缺点:资源利用率高了,但是,每次调用getInstance()时都会执行Synchronized同步,并发效率低

三、双重检查锁式实现(不建议使用)

package com.hezeu.singleton;

/**
*@Classname Singleton03
*@Description 双重检查锁机制
*@Date 2020/2/19下午04:03
*@Created by朱进博 [email protected]
*/
public class SingletonDemo03{
	private static SingletonDemo03 instance =  null;

	private SingletonDemo03(){}

	public static SingletonDemo03 getInstance(){
		if(instance==null){
			SingletonDemo03sc=null;
			synchronized(SingletonDemo03.class){
				if(sc==null){
					synchronized(SingletonDemo03.class){
						if(sc==null){
						sc=newSingletonDemo03();
						}
					}
					instance=sc;
				}
			}
		}
		return instance;
	}
}

优点:双重检查锁式将同步内容下放到if内部,提高了执行的效率,不必每次获取对象时都同步,只有第一次才同步。
缺点:由于编译器优化问题和JVM底层内部模型原型,偶尔出现问题

优化版:

package com.hezeu.singleton;

/**
*@Classname SingletonDemo07
*@Description 优化双重检查锁式
*@Date 2020/2/20下午11:05
*@Created by 朱进博 [email protected]
*/
public class SingletonDemo07{
	private static volatile SingletonDemo07 instance;

	private SingletonDemo07(){}

	public static SingletonDemo07 getInstance(){
		if(instance == null){
			synchronized(SingletonDemo07.class){
				if(instance == null){
					instance=newSingletonDemo07();
				}
			}
		}
		return instance;
	}
}

双重检查锁模式进行了两次判断,第一次避免不要的实例,第二次为了进行同步,并避免了多线程问题,又由于实例化对象创建可能出现JVM重排,在多线程访问存在风险,因此使用volatile修饰instance实例变量,解决该问题

四、静态内部类式实现(线程安全、效率高、支持延时加载,也是一种懒加载)

package com.hezeu.singleton;

/**
*@Classname SingletonDemo04
*@Description 静态内部类实现单例模式
*@Date 2020/2/19 下午04:09
*@Created by 朱进博 [email protected]
*/
public class SingletonDemo04{
	private static class SingletonClassInstance{
		private static final SingletonDemo04 instance = new SingletonDemo04();
	}
	public static SingletonDemo04 getInstance(){
		Return SingletonClassInstance.instance;
	}
	private SingletonDemo04(){}
}

要点:外部类没有static属性,不会像饿汉式那样立即加载对象
优点:只有真正调用getInstance(),才会加载静态内部类,加载类时是线程安全的。
Instance是static final类型,保证了内存中只有这样一个实例存在,而且只能赋值一次,从而保证了线程安全。

五、枚举实现单例模式

package com.hezeu.singleton;

/**
*@Classname SingletonDemo05
*@Description 枚举实现单例模式
*@Date 2020/2/19 下午04:14
*@Created by 朱进博 [email protected]
*/
public enum SingletonDemo05{
	INTEGER;//枚举元素,代表一个Singleton实例

	//自己的操作
	public void singletonOperation(){
	//功能处理
	}
}

优点:实现简单
枚举本身就是单例。JVM底层提供保障,避免通过反射和序列化的漏洞!
缺点:不支持延时加载

在这里插入图片描述

反射、反序列化破解单例模式:

package com.hezeu.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
*@Classname Client02
*@Description 测试反射和反序列化破解单例模式
*@Date 2020/2/19 下午04:20
*@Created by 朱进博 [email protected]
*/
public class Client02{
	public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException,IllegalAccessException,InvocationTargetException,InstantiationException,IOException{
		SingletonDemo06s1=SingletonDemo06.getInstance();
		SingletonDemo06s2=SingletonDemo06.getInstance();

		Classclazz = Class.forName("com.hezeu.singleton.SingletonDemo06");
		Constructor<SingletonDemo06> c = clazz.getDeclaredConstructor(null);
		c.setAccessible(true);
		SingletonDemo06s3 = (SingletonDemo06)c.newInstance();
		System.out.println(s3==s2);

		FileOutputStream fos = new FileOutputStream("f:/1.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(s1);
		oos.flush();
		oos.close();
			
		ObjectInputStreamo is = new ObjectInputStream(new FileInputStream("F:/1.txt"));
		SingletonDemo06s4 = (SingletonDemo06)ois.readObject();
		System.out.println(s4==s1);
	}
}

解决方案:
反射:可以在构造方法中手动抛出异常,判断如果已经实例化则抛出异常
反序列化:可以通过定义readResolve()防止获得不同对象。
----反序列化时,如果对象所在类定义了readResolve,定义返回那个对象

单例模式效率测试:

package com.hezeu.singleton;

import java.util.concurrent.CountDownLatch;

/**
*@Classname Client03
*@Description 测试单例模式效率
*@Date 2020/2/19 下午 04:38
*@Createdby 朱进博 [email protected]
*/
public class Client03{
	public static void main(String[] args) throws InterruptedException{
		int threadNum=10;
		CountDownLatch countDownLatch = new CountDownLatch(threadNum);
		long start = System.currentTimeMillis();
		for(inti=0;i<threadNum;i++){
			new Thread(new Runnable(){
				@Override
				public void run(){
					for(inti=0;i<11000000;i++){
					//SingletonDemo01instance=SingletonDemo01.getInstance();//饿汉式47
					//SingletonDemo02instance=SingletonDemo02.getInstance();//懒汉式3816
					//SingletonDemo04instance=SingletonDemo04.getInstance();//静态内部类18
					//SingletonDemo05instance=SingletonDemo05.INSTANCE;//枚举46
					//SingletonDemo07instance=SingletonDemo07.getInstance();//双重检查锁36
					}
					countDownLatch.countDown();
				}
			}).start();
		}
		countDownLatch.await();
		Long end=System.currentTimeMillis();
		System.out.println("时间--->"+(end-start));
	
	}
}

饿汉式 47ms
懒汉式 3816ms
静态内部类 18ms
枚举式 46ms
静态内部类 18ms
双重检查锁式 36ms

CountDownLatch:
同步辅助类,在完成一组正在其他线程中执行操作之前,它允许一个或多个线程一直等待 countDown()
当前线程调用此方法,则计数减一 Await() 调用此方法会一直阻塞当前线程,直到计时器值为0

发布了3 篇原创文章 · 获赞 1 · 访问量 59

猜你喜欢

转载自blog.csdn.net/weixin_44789225/article/details/104420908