设计模式 | 单例模式

1.核心作用

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

2.常见场景

在这里插入图片描述

3.单例模式的优点

在这里插入图片描述

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

在这里插入图片描述

4.1 饿汉式

步骤:

  1. 私有化构造器
  2. 类初始化时,立即加载对象【不涉及线程安全问题】
  3. 提供获取该对象的方法【没有Synchronized,效率高】


// 恶汉式

public class SingletonDemo1 {
    private SingletonDemo1(){};

    private static SingletonDemo1 instance= new SingletonDemo1();

    public static SingletonDemo1 GetInstance(){
        return instance;
    }
}


class SingletonDemo1Test {
    public static void main(String[] args) {
        SingletonDemo1 instance1 = SingletonDemo1.GetInstance();
        SingletonDemo1 instance2 = SingletonDemo1.GetInstance();

        boolean b = instance1 == instance2;
        System.out.println(b);
        //输出:true
    }
}

优点: 线程安全,调用效率高

缺点: 不能延时加载,如果长时间不用,会造成资源浪费

解决方案: 懒汉式

4.2 懒汉式

步骤:

  1. 私有化构造器
  2. 类初始化时,不立即加载对象
  3. 提供获取该对象的方法,在该方法上用Synchronized关键字,解决多线程冲突问题
//懒汉式
public class SingletonDemo2 {
    //1. 私有化构造器
    private  SingletonDemo2(){};

    //2. 类初始化时,不立即加载对象
    private static SingletonDemo2 instance;

    //3. 提供获取该对象的方法,在该方法上用Synchronized关键字,解决多线程冲突问题
    public static synchronized SingletonDemo2 getInstance(){
        if(instance!=null){
                return new SingletonDemo2();
            }
            return instance;
    }

}

class SingletonDemo2Test {
    public static void main(String[] args) {
        SingletonDemo2 instance1 = SingletonDemo2.getInstance();
        SingletonDemo2 instance2 = SingletonDemo2.getInstance();

        boolean b = instance1 == instance2;
        System.out.println(b);
        //输出:true
    }
}

优点: 延时加载,即在调用方法时产生对象,解决恶汉式资源浪费的问题。
缺点: Synchronized关键字会使代码效率变低
解决方案: DCL懒汉式,即双重检测锁模式,解决代码效率问题。

4.3 DCL懒汉式(双重检测锁模式)

步骤:

  1. 私有化构造器
  2. 类初始化时,不立即加载对象
  3. 提供获取该对象的方法,用volatile关键字避免指令重排带来的问题;缩小Synchronized代码块范围,解决代码效率问题。
//DCL懒汉式(双重检测锁模式)
public class SingletonDemo3 {
    //私有化构造器
    private SingletonDemo3() {

    }

    //只提供一个实例,并不创建对象
    //使用避免指令重排带来的线程安全问题
    //volatile:对于同一个变量,在一个线程中值发生了改变,则在另一个线程中立即生效,可以大幅度避免下面的问题,不排除极端情况
    private static volatile SingletonDemo3 instance;

    //提供公共的获取方法,因为不是在类加载时就创建对象,因此存在线程安全问题,使用同步代码块提高效率
    //现在不需要对整个方法进行同步,缩小了锁的范围,只有第一次会进入创建对象的方法,提高了效率
    //当第一个线程执行到创建对象的方法时,但还未出方法返回,此时第二个线程进入,发现instance不为空,但第一个线程此时还未出去,可能发送意想不到的安全问题
    public static SingletonDemo3 getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo3.class) {
                if (instance == null) {
                    instance = new SingletonDemo3();
                }
            }
        }
        return instance;
    }
}

//测试
class SingletonDemo3Test {
    public static void main(String[] args) {
        SingletonDemo3 instance = SingletonDemo3.getInstance();
        SingletonDemo3 instance1 = SingletonDemo3.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

问题:为什么需要两次判断if(singleTon==null)?

分析

  • 第一次校验:由于单例模式只需要创建一次实例,如果后面再次调用getInstance方法时,则直接返回之前创建的实例,因此大部分时间不需要执行同步方法里面的代码,大大提高了性能。如果不加第一次校验的话,那跟上面的懒汉模式没什么区别,每次都要去竞争锁。

  • 第二次校验:如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。

  • 需要注意的是, private static volatile SingletonDemo3 instance;需要加volatile关键字,否则会出现错误问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

4.4 静态内部类

步骤:

  1. 私有化构造器
  2. 使用静态内部类(实现延迟加载)
  3. 提供获取该对象的方法

静态内部类:同样也是利用了类的加载机制,它与饿汉模式不同的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。

public class SingletonDemo4 {
    private SingletonDemo4(){};

    private static  class InnerClass{
        private static final SingletonDemo4 instance=new SingletonDemo4();
    }

    public static SingletonDemo4 getInstance(){
        return InnerClass.instance;
    }
}

优点: 线程安全,调用效率高,可延时加载

缺点: 反射机制可以轻易破坏上述单例

class SingletonDemo4Test {
    public static void main(String[] args) throws Exception {
        SingletonDemo4 instance1 = SingletonDemo4.getInstance();
        
        Constructor<SingletonDemo4> declaredConstructor = SingletonDemo4.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        SingletonDemo4 instance2 = declaredConstructor.newInstance();

        System.out.println(instance1==instance2);
        //输出:false
    }
}

由上面的例子可以得出,反射是可以破坏以上四种的单例模式(这里不一一演示)

那怎样才能解决这个问题呢,我们来看一下反射创建对象的newInstance()方法:

在这里插入图片描述
从源码中可以看出,当反射遇到枚举时直接抛出异常,因此,枚举是创建单例的不二之选

4.5 枚举

原理:在反射的源码中,我们发现,反射天然屏蔽枚举类型,所以枚举的对象纯天然是单例的。

public enum  SingletonDemo5 {
    INSTANCE;

    public SingletonDemo5 getInstance(){
        return INSTANCE;
    }
}
class SingletonDemo5Test {
    public static void main(String[] args) {
        SingletonDemo5 instance1 = SingletonDemo5.INSTANCE;
        SingletonDemo5 instance2 = SingletonDemo5.INSTANCE;

        boolean b = instance1 == instance2;
        System.out.println(b);
        //输出:true
    }
}

优点: 线程安全,调用效率高,

缺点: 不能延时加载

5.五种实现单例模式的方式的对比

饿汉式:线程安全(不排除反射),调用效率高,不能延时加载
懒汉式:线程安全(不排除反射),调用效率不高,可以延时加载
DCL懒汉式:由于JVM底层模型原因,偶尔出现问题,不建议使用
静态内部类式:线程安全(不排除反射),调用效率高,可以延时加载
枚举单例:线程安全,调用效率高,不能延时加载,避免反射带来的问题。

参考资料:单例模式的五种实现方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 支持向量机非线性回归通用MATLAB程序解析 #### 一、概述 本文将详细介绍一个基于MATLAB的支持向量机(SVM)非线性回归的通用程序。该程序采用支持向量机方法来实现数据的非线性回归,并通过不同的核函数设置来适应不同类型的数据分布。此外,该程序还提供了数据预处理的方法,使得用户能够更加方便地应用此程序解决实际问题。 #### 二、核心功能与原理 ##### 1. 支持向量机(SVM) 支持向量机是一种监督学习模型,主要用于分类和回归分析。对于非线性回归任务,SVM通过引入核技巧(kernel trick)将原始低维空间中的非线性问题转换为高维空间中的线性问题,从而实现有效的非线性建模。 ##### 2. 核函数 核函数的选择直接影响到模型的性能。本程序内置了三种常用的核函数: - **线性核函数**:`K(x, y) = x'y` - **多项式核函数**:`K(x, y) = (x'y + 1)^d` - **径向基函数(RBF)**:`K(x, y) = exp(-γ|x - y|^2)` 其中RBF核函数被广泛应用于非线性问题中,因为它可以处理非常复杂的非线性关系。本程序默认使用的是RBF核函数,参数`D`用于控制高斯核函数的宽度。 ##### 3. 数据预处理 虽然程序本身没有直接涉及数据预处理的过程,但在实际应用中,对数据进行适当的预处理是非常重要的。常见的预处理步骤包括归一化、缺失值处理等。 ##### 4. 模型参数 - **Epsilon**: ε-insensitive loss function的ε值,控制回归带宽。 - **C**: 松弛变量的惩罚系数,控制模型复杂度与过拟合的风险之间的平衡。 #### 三、程序实现细节 ##### 1. 函数输入与输出 - **输入**: - `X`: 输入特征矩阵,维度为(n, l),其中n是特征数量,l是样本数量。 - `Y`: 目标值向量,长度为l。 - `Epsilon`: 回归带宽。 - `C`: 松弛变量的惩罚系数。 - `D`: RBF核函数的参数。 - **输出**: - `Alpha1`: 正的拉格朗日乘子向量。 - `Alpha2`: 负的拉格朗日乘子向量。 - `Alpha`: 拉格朗日乘子向量。 - `Flag`: 标记向量,表示每个样本的类型。 - `B`: 偏置项。 ##### 2. 核心代码解析 程序首先计算所有样本间的核矩阵`K`,然后构建二次规划问题并求解得到拉格朗日乘子向量。根据拉格朗日乘子的值确定支持向量,并计算偏置项`B`。 - **核矩阵计算**:采用RBF核函数,通过`exp(-(sum((xi-xj).^2)/D))`计算任意两个样本之间的相似度。 - **二次规划**:构建目标函数和约束条件,使用`quadprog`函数求解最小化问题。 - **支持向量识别**:根据拉格朗日乘子的大小判断每个样本是否为支持向量,并据此计算偏置项`B`。 #### 四、程序扩展与优化 - **多核函数支持**:可以通过增加更多的核函数选项,提高程序的灵活性。 - **自动调参**:实现参数自动选择的功能,例如通过交叉验证选择最优的`Epsilon`和`C`值。 - **并行计算**:利用MATLAB的并行计算工具箱加速计算过程,特别是当样本量很大时。 #### 五、应用场景 该程序适用于需要进行非线性回归预测的场景,如经济预测、天气预报等领域。通过调整核函数和参数,可以有效应对各种类型的非线性问题。 ### 总结 本程序提供了一个支持向量机非线性回归的完整实现框架,通过灵活的核函数设置和参数调整,能够有效地处理非线性问题。对于需要进行回归预测的应用场景,这是一个非常实用且强大的工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值