菜鸟修行之路----设计模式:单例模式
前言:
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月 成都