23种软件设计模式完整教程
单例模式(Singleton Pattern)
使用场景:
独一无二
确保一个类在任何情况下都绝对只有一个实例,并只提供一个全局访问点。
优缺点
优:保证内存中只有一个实例,减少了内存开销,避免对资源的过多占用。
类图:
略
源码实例:
ServletContext、ServletContextConfig、在 Spring 框架应用中 ApplicationContext、数据库连接池。
使用建议:
建议使用内部类创建单例模式、兼顾性能与线程安全。
实现方式(4种):
(一)懒汉式:
优点:使用时创建实例、不浪费内存空间
缺点:部分实现方式线程不安全、通过加锁虽保证线程安全但影响性能。
package com.knowledge.system.software_design_pattern.singleton_pattern.course_instance.lazy_singleton;
/**
* @program: demo-pom
* @description: 懒汉式
* @author: bo.hu
* @create: 2020-01-19 16:32
**/
public class LazySingleton<Syncchronized> {
private static LazySingleton lazySingleton=null;
private LazySingleton(){}
/**
* 第一种实现方式
* @return
*/
public static LazySingleton getInstance(){
if(null==lazySingleton){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
/**
* 第二种实现方式
*/
public synchronized static LazySingleton getInstance1(){
if(null==lazySingleton){
lazySingleton=new LazySingleton();
}
return lazySingleton;
}
/**
* 第三种实现方式
*/
public static LazySingleton getInstance2(){
if(null==lazySingleton){
synchronized (LazySingleton.class){
if(null==lazySingleton){
lazySingleton=new LazySingleton();
}
}
}
return lazySingleton;
}
}
(二)饿汉式:
优点:无锁、执行效率高
缺点:占着茅坑不拉屎,实例未使用也会被创建,浪费内存空间
package com.knowledge.system.software_design_pattern.singleton_pattern.course_instance.hungry_singleton;
/**
* @program: demo-pom
* @description: 饿汉式
* @author: bo.hu
* @create: 2020-01-19 16:24
**/
public class HungrySingleton {
private static final HungrySingleton hungry=new HungrySingleton();
/*
饿汉式方式二:
private static final HungrySingleton hungry=null;
static {
hungry=new HungrySingleton();
}*/
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungry;
}
}
(三)内部类:
优点:类加载时创建实例、线程绝对安全、可防止反射式破坏单例模式
缺点:依旧会被序列化方式破坏、依旧存在占着茅坑不拉屎
package com.knowledge.system.software_design_pattern.singleton_pattern.course_instance.innerclass_singleton;
/**
* @program: demo-pom
* @description: 内部类实现的单例模式
* @author: bo.hu
* @create: 2020-01-19 17:18
**/
/**
* 史上最牛B单例模式---实现;不过 依旧会被序列化破坏 序列化:内存-->磁盘 反序列化:磁盘-->内存【重新分配内存空间】 违背了单例设计原则
*/
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton(){
//这段逻辑主要是为了防止反射破坏单例模式
if(null!=LazyHolder.instance){
throw new RuntimeException("不允许创建多个实例");
}
}
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.instance;
}
private static class LazyHolder{
private static final LazyInnerClassSingleton instance=new LazyInnerClassSingleton();
}
}
(四)注册式实现:
(1)枚举方式实现:
优点:完全避免反射式、序列化式破坏
缺点:暂时没有缺点
package com.knowledge.system.software_design_pattern.singleton_pattern.course_instance.enum_singleton;
/**
* @program: demo-pom
* @description: 枚举单例模式
* @author: bo.hu
* @create: 2020-01-19 18:07
**/
/**
* 完全规避了 反射问题与序列化问题
*/
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
(2)容器缓存实现:
优点:暂无、适用与出创建实例非常多的情况
缺点:非线程安全
package com.knowledge.system.software_design_pattern.singleton_pattern.course_instance.container_singleton;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 容器式单例模式---适用于创建实例非常多的情况,便于管理
*/
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String ,Object> ioc=new ConcurrentHashMap <String,Object>();
public static Object getBean(String className){
synchronized (ioc){
if(!ioc.containsKey(className)){
Object o=null;
try {
o=Class.forName(className).newInstance();
ioc.put(className,o);
} catch (Exception e) {
e.printStackTrace();
}
return o;
}else{
return ioc.get(className);
}
}
}
}
两种破坏(反射破坏、序列化破坏)方式解释:
反射式破坏:输出值不相等。
public class Test {
public static void main(String[] args) {
try {
Class cls=LazyInnerClassSingleton.class;
Constructor c=cls.getDeclaredConstructor(null);
c.setAccessible(true);
Object o1=c.newInstance();
Object o2=c.newInstance();
System.out.println(o1==o2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("测试");
}
}
序列化式破坏:
输出值不相等,即使加了readResolve方法,反序列化时也会创建两个实例,只不其中一个没有被返回,没有从本质上解决问题。
class SeriableSingleton implements Serializable{
public final static SeriableSingleton INSTANCE=new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
//该方法可以防止 序列化方式破坏单例模式---不加该方法---输出值不相等
private Object readResolve(){
return INSTANCE;
}
}
class SeriableSingletonTest{
public static void main(String[] args) {
try {
SeriableSingleton s1=null;
SeriableSingleton s2=SeriableSingleton.getInstance();
FileOutputStream fos=null;
fos=new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis=new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois=new ObjectInputStream(fis);
s1=(SeriableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
原因:
反序列化时,判断如果有构造方法直接创建对象,再判断有没有readResolve方法,有就执行该方法内容,所以有上面的解决方法。