单例模式

属于创建者模式,提供了一种创建对象的方式

单例有两种设计形式
饿汉式 -- 类加载的时候,这个对象就会被创建
懒汉式 -- 只有首次使用的时候,才会创建对象

饿汉式

想要在类加载的时候创建对象,有两种方式
一种是在静态变量初始化时,赋值为一个对象
另一种是在静态代码块里给静态变量赋值为一个对象

优点

  • 线程安全,因为实例是在类加载过程中被创建的,类加载过程在JVM中是线程安全的

缺点

  • 这两种方式都可能造成内存浪费问题,因为类加载时就创建对象了,如果对象很大,又一直没使用,就很占空间
// 方式一:静态变量创建对象
public class Singleton {

    private Singleton() {};

    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
}
// 方式二:使用静态代码块创建对象
public class Singleton {

    private Singleton() {};

    private Singleton instance;

    static {
        instance = new Singleton();
    }

    public static Singleton getInstance() {
        return instance;
    }
}

懒汉式

调用静态方法时才会创建对象,所以将创建对象写在静态方法里
(类加载过程中静态方法会被加载到内存中,但只有在被调用时才会执行)

  1. 线程不安全写法
public class Singleton {
    
    private Singleton() {};

    private static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    } 
}
  1. 加同步锁,线程安全

缺点
对整个方法加了同步锁,效率低,每个时间点只有一个线程能执行这个方法

  • 这个同步锁加在静态方法上,相当于锁住了class对象(类对象),这个是会影响到其它的加了同步锁的静态方法执行,因为只有一个类对象
  • 但对于单例对象来说无所谓,因为它就这一个静态方法
public class Singleton {
    
    private Singleton() {};

    private static Singleton instance;

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            // 位置 1
            instance = new Singleton();

        }

        return instance;
    } 
}
  • 这个线程安全问题是在初始化instance时出现的,我们担心会有多个线程同时进入位置1(这是并行,如果并发该这么说:当一个线程执行到位置1时,切换到了另一个线程,那他们两个都能创建对象),从而导致new了多个实例,当初始化之后就不用担心线程安全问题了,所以没必要让每个线程持有锁才能调用这个方法
  1. 双重检查锁

这个双重检查锁的目的就是为了当instance初始化之后,其它线程不用再去拿锁或者等待锁了

public class Singleton {
    private Singleton() {};

    private static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}

但这还是有问题,因为instance = new Singleton();可能会发生指令重排
为什么会发生指令重排?
因为jvm在实例化对象的时候
这行代码,在字节码指令中有两步

    1. 调用构造方法
    1. 将这个对象的引用赋值给instance

从jvm的角度来说

    1. 给对象分配内存
    1. 初始化
    1. 将对象地址赋值给变量

如果先执行了第2步,会给instance赋值一个未初始化的对象,然后这个时候这个线程时间片到了,另一个线程执行这个方法会直接返回这个未初始化的对象,所以为了保证先执行第一步,需要添加volatile关键字

  1. 防指令重排
public class Singleton {
    private Singleton() {};

    private static volatile Singleton instance;

    public static Singleton getInstance() {
        
        if (instance == null) {

            synchronized (Singleton.class) {
                
            }
        }

        return instance;
    }
} 
  1. 静态内部类
    线程安全,因为类加载是线程安全的
public class Singleton {
    private Singleton() {};

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
  1. 枚举方式
    线程安全,只会被装载一次,且不会被破坏
    在类加载时加载,且所有实例是在类加载阶段被初始化的,且只会被初始化一次
    饿汉式
public enum Singleton {
    INSTANCE;   // 枚举常量,也叫枚举实例
}

工厂模式

简单工厂模式

包括
抽象产品
具体产品
具体工厂

优点
将对象创建与业务逻辑分开,可以避免修改客户端代码,且更好扩展

缺点
增加新产品时,需要改变具体工厂的代码,违反了“开闭原则”

工厂方法模式

包括
抽象工厂
具体工厂
抽象产品
具体产品

具体来说,具体类实现或者继承抽象类,具体工厂实例化对象,
然后会有一个中间类使用多态的方式根据输入是什么工厂就返回一个什么实例
客户端想要什么产品就构建一个什么工厂对象,通过中间类得到该产品

优点
将对象创建与业务逻辑分开,实现代码解耦,实现在不修改客户端代码的情况下能更好扩展
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

适用
适用于生产同一类产品,他们都有一个公共的特征

工厂模式应用

在我了解的JDK源码中,单列集合获取迭代器的方法就使用到了工厂方法模式
Collention是抽象工厂、ArrayList是具体工厂,Iterator是抽象产品、ArrayList类中的Iter内部类是具体的产品;
在ArrayList的源码中,他有一个iterator方法生产Iter对象,所以我们可以直接使用这个iterator方法得到集合中的元素

// ArrayList.java

public Iterator<E> iterator() {
        return new Itr();
}

/**
 * An optimized version of AbstractList.Itr
 */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    Itr() {}

    ...
}

抽象工厂

每一类产品多了一个等级的维度

策略模式

行为型模型

定义了一系列算法,并把每个算法封账,可以相互替换

把算法的使用 与 算法的实现分开,并委派给不同的对象对这些算法进行管理

主要包括:
抽象策略
具体策略
环境

使用场景:
一个系统需要动态地在几种算法中选择一种,可以将每个算法封装到策略中
甚至可以用户自定义策略,根据这个策略实现具体功能

应用举例:
Arrays.sort()里面使用了 策略模式,在这个方法里我们需要传递一个Comparator接口的实现类,通过这个实现类我们可以自定义排序
Comparator接口就是抽象策略,
具体策略就是我们自己写的Comparator接口实现类
Arrays就是一个环境角色类

public class Arrays{
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
}

责任链模式

如果处理一个事情,有多个流程,每个流程处理完要传递给下一个流程,要所有流程处理成功才算处理成功

简单的写法就是使用if else一直传递

使用责任链可以代码解耦,将每个流程处理封装一个类,处理完则传给下一个流程类,这样符合类的单一职责原则

我这里就是把if else给拆了出来,实现代码解耦,更有扩展性

应用

Javaweb中的 拦截器、过滤器中都采用的责任链
比如,对请求过滤会使用责任链将请求传给下一个处理器

还有一个预处理和后处理的方法
它们实现方式是递归

posted on 2023-10-21 23:09  Cheyaoyao  阅读(21)  评论(0编辑  收藏  举报