菜鸟修行之路----设计模式:单例模式

菜鸟修行之路----设计模式:单例模式

前言:

java语言基础部分告一段落了,接下来就进入java进阶篇:设计模式+框架。

1.设计模式基础

简单的来说:设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。

使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。

1.1 设计模式的六大原则

1、开闭原则(Open Close Principle)

对扩展开放,对修改关闭

2、里氏代换原则(Liskov Substitution Principle)

对实现抽象化的具体步骤的规范,(子类尽量不要重写和重载父类方法)

3、依赖倒转原则(Dependence Inversion Principle)

对象接口编程,依赖于抽象。

4、接口隔离原则(Interface Segregation Principle)

4、接口隔离原则(Interface Segregation Principle)

降低类之间的耦合度。拆出子类不需要的抽象方法。

5、迪米特法则,又称最少知道原则(Demeter Principle)

一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。(封装)

6、合成复用原则(Composite Reuse Principle)

尽量使用合成/聚合的方式,而不是使用继承。

1.2 设计模式的分类

根据模式的目的(用于完成什么样的工作)可以将23种设计模式分为以下3类:

  • 创建型:模式与对象的创建相关。
  • 结构型:模式用于处理类或对象的组合。
  • 行为型:模式对类或者对象怎样交互和怎样分配职责(行为)进行描述。

具体分类如下所示:

类型具体设计模式
创建型模式工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
结构型模式适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行为型模式责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)

1.3 设计模式之间的关系

用一幅经典图来描述设计模式之间的关系。

在这里插入图片描述

1.4 设计模式的学习思路

对于任何一种设计模式的学习,我们主要掌握以下几点:

  • 意图:也可以理解为定义和核心思想,目的。
  • 作用:用于解决哪一类问题。
  • 结构:该种模式的具体结构。(UML类图)
  • 关键代码:该模式的核心代码。
  • 优缺点
  • 应用场景:常见应用场景。
  • 具体实现
  • 应用实例

2.单例模式(Singleton Pattern)

2.1 意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2.2 作用

主要解决当一个全局使用的类频繁的创建与销毁这类问题。

2.3 结构

结构如图:

在这里插入图片描述

2.4 关键代码

  • 构造方法私有化;
  • 实例化的变量引用私有化;
  • 获取实例的方法共有

2.5 优缺点

优点

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

2.6 应用场景

  • 要求生产唯一序列号。
  • WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。(例如:网页在线人数)
  • 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

2.7 具体实现

对于单例模式来说,具体实现方式有很多种。具体可以归纳为以下7种:

2.7.1饿汉式单例

核心:类被加载时,单例对象已经创建 (类实例化前已经创建)

缺点:不可控,可能造成极大的内存浪费(单例对象较多时),增大内存开销
优点:线程安全

class HungrySingleton{
	//类被加载时,单例对象已经创建 (类实例化前已经创建)
	private static final HungrySingleton instance =new HungrySingleton();
	 
    //构造方私有化法	
	private HungrySingleton() {}
	//提供一个全局访问点
	public static HungrySingleton getInstance () {return instance;}
}

2.7.2 静态饿汉式单例

饿汉式的一个简单扩展,将类的实例化放在静态代码块中。

class HungrystaticSingleton{
	//类被加载时,单例对象已经创建 
	private static final HungrystaticSingleton instance;
	 
	static {
		instance =new HungrystaticSingleton();
	}
    //构造方私有化法	
	private HungrystaticSingleton() {}
	//提供一个全局访问点
	public static HungrystaticSingleton getInstance () {return instance;}
}
2.7.3 懒汉式单例

延迟加载,用的时候在创建。启动速度快。

优点 :解决了资源浪费问题
缺点:增加了个Instance()方法的逻辑复杂性,可能出现线程安全问题(线程不安全,在多线程情况下出现多个实例)

class LazySimpleSingleton{
	 
	 private static LazySimpleSingleton instance =null;
	 
	 private LazySimpleSingleton () {}
	 
	 public static LazySimpleSingleton getInstance () {
		 if(instance==null)
			 instance=new LazySimpleSingleton();
		 return instance;
	 } 
 }
2.7.4 线程安全的懒汉式单例

增加同步锁(同步方法),解决多线程下的线程安全问题。

缺点:性能下降。

class LazySimpleSingleton01{
	 
	 private static LazySimpleSingleton01 instance =null;
	 
	 private LazySimpleSingleton01() {}
	 
	 public  synchronized static LazySimpleSingleton01 getInstance () {
		 if(instance==null)
			 instance=new LazySimpleSingleton01();
		 return instance;
	 } 
 }
2.7.5 同步代码块的饿汉式单例

同步代码块(创建实例部分),提升了部分性能。

class LazyDoubleCheckSingleton{
	private  static LazyDoubleCheckSingleton instance =null;
	
	private LazyDoubleCheckSingleton() {}
	
	public static LazyDoubleCheckSingleton getInstance() {
		//第二次访问可以优化
		if(instance==null) {
			//阻塞,没有彻底解决
			synchronized(LazyDoubleCheckSingleton.class) {   //同步锁
				if(instance==null)
					 instance=new LazyDoubleCheckSingleton();
			}
		}
	  return instance;
	}
	
}

采用双重检查锁

class LazyDoubleCheckSingleton01{
	// 注意:这里有 volatile 关键字修饰
    private  static volatile LazyDoubleCheckSingleton instance =null;
	
	private LazyDoubleCheckSingleton() {}
	
	public static LazyDoubleCheckSingleton getInstance() {
		//第二次访问可以优化
		if(instance==null) {
			//阻塞,没有彻底解决
			synchronized(LazyDoubleCheckSingleton.class) {   //同步锁
				if(instance==null)
					 instance=new LazyDoubleCheckSingleton();
			}
		}
	  return instance;
	}
	
}

优点:线程安全;延迟加载;效率较高。

由于 JVM 具有指令重排的特性,在多线程环境下可能出现 instance已经赋值但还没初始化的情况,导致一个线程获得还没有初始化的实例。volatile 关键字的作用:

  • 保证了不同线程对这个变量进行操作时的可见性
  • 禁止进行指令重排序
2.7.6 静态内部类的单例模式
class LazyInnerClassSingleton{
	private LazyInnerClassSingleton() {
		if(LazyHolder.instance!=null) {
			throw new RuntimeException("");
		}
	}
	public static LazyInnerClassSingleton getInstance() {
		return LazyHolder.instance;
	}
	public static class LazyHolder{
		private static final LazyInnerClassSingleton instance=new LazyInnerClassSingleton();
	}
}

优点:避免了线程不安全,延迟加载,效率高。

静态内部类的方式利用了类装载机制来保证线程安全,只有在第一次调用getInstance方法时,才会装载SingletonInstance内部类,完成Singleton的实例化,所以也有懒加载的效果。

2.7.7 注册式单例

采用枚举的方式,放在map容器里面,注册一个放一个。(JDK中使用的就是注册式单例)

不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象

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.8 单例模式的典型应用

2.8.1 Spring AbstractFactoryBean

AbstractFactoryBean 类源码如下:

public final T getObject() throws Exception {
    if (this.isSingleton()) {
        return this.initialized ? this.singletonInstance : this.getEarlySingletonInstance();
    } else {
        return this.createInstance();
    }
}

private T getEarlySingletonInstance() throws Exception {
    Class<?>[] ifcs = this.getEarlySingletonInterfaces();
    if (ifcs == null) {
        throw new FactoryBeanNotInitializedException(this.getClass().getName() + " does not support circular references");
    } else {
        if (this.earlySingletonInstance == null) {
            // 通过代理创建对象
            this.earlySingletonInstance = Proxy.newProxyInstance(this.beanClassLoader, ifcs, new AbstractFactoryBean.EarlySingletonInvocationHandler());
        }
        return this.earlySingletonInstance;
    }
}
2.8.2 Mybatis ErrorContext ThreadLocal

ErrorContext 类,通过 ThreadLocal 管理单例对象,一个线程一个ErrorContext对象,ThreadLocal可以保证线程安全

public class ErrorContext {
    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
    private ErrorContext() {
    }

    public static ErrorContext instance() {
        ErrorContext context = LOCAL.get();
        if (context == null) {
          context = new ErrorContext();
          LOCAL.set(context);
        }
        return context;
    }
    //...
}
2.8.3 Mybatis 中用于获取SqlSession对象

采用单例模式构建,确保只有一个SqlSession对象存在。

/**
 *构建SqlSessionFactory和获取SqlSession对象工具类
 * 采用单例模式构建,确保只有一个SqlSession对象存在。
 */
public class SqlSessionFactoryUtils {
    private final static Class<SqlSessionFactoryUtils> LOCK=SqlSessionFactoryUtils.class;
    private static SqlSessionFactory sqlSessionFactory=null;

    /**
     *构造方法添加private关键字,确保唯一性,使其它类或者方法不能通用new来创建该类对象。
     */
    private SqlSessionFactoryUtils(){}
    public static SqlSessionFactory getSqlSessionFactory(){
        synchronized (LOCK){
            if(sqlSessionFactory!=null){
                return sqlSessionFactory;
            }
            String resource="mybatis_cfg.xml";
            InputStream inputStream=null;
            try {
                inputStream= Resources.getResourceAsStream(resource);
                sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
            return sqlSessionFactory;
        }
    }

    public static SqlSession openSqlSession(){
        if(sqlSessionFactory==null){
            getSqlSessionFactory();
        }
        return sqlSessionFactory.openSession();
    }

} 

修行之路艰辛,与君共勉
2020年3月 成都

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值