单例设计模式

模式定义

保证一个类只有一个实例,并且提供一个全局访问点

场景

重量级的对象,不需要多个实例,如线程池,数据库连接池。

结构

提供了一个私有的属性
提供了一个私有的构造函数
提供了一个全局访问点

具体实现

懒汉模式

延迟加载,只有在真正使用的时候,才开始实例化。类似于懒加载

  1. 线程安全问题
  2. double check 加锁优化
  3. 编译器,CPU有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰,对于volatile修饰的字段,可以防止指令重排

例子

单线程

public class LazySingletonTest {
    public static void main(String[] args) {
        LazySingleton a1 = LazySingleton.getinstance();
        LazySingleton a2 = LazySingleton.getinstance();
        LazySingleton a3 = LazySingleton.getinstance();

        System.out.println(a1==a2);
        System.out.println(a2 == a3);
    }

}

class LazySingleton {
    private static LazySingleton instance;//提供一个私有的属性
    private LazySingleton(){

    }//构造方法

    public static LazySingleton getinstance(){
        if(instance==null){
            instance=new LazySingleton();
        }
        return instance;
    }

}

在这里插入图片描述

多线程

public class LazySingletonTest {
    public static void main(String[] args) {
        new Thread(() -> {
            LazySingleton a = LazySingleton.getinstance();
            System.out.println(a);

        }).start();
        new Thread(() -> {
            LazySingleton a = LazySingleton.getinstance();
            System.out.println(a);

        }).start();
    }

}

class LazySingleton {
    private static LazySingleton instance;//提供一个私有的属性
    private LazySingleton(){

    }//构造方法

    public static LazySingleton getinstance(){
        if(instance==null){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance=new LazySingleton();
        }
        return instance;
    }

}

在这里插入图片描述

使用多线程的话 两个线程都会进行相应实例的创建。
破话了单例的定义。

解决方法
在getinstance方法上加锁 加锁是为了保证 只有一个线程 new出实例

  public synchronized static LazySingleton getinstance(){
        if(instance==null){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance=new LazySingleton();
        }
        return instance;
    }

在这里插入图片描述

优化
因为 不能每次调用这个方法 都要去加锁 所以 我们在判断它为空之后 在去加锁
volatile 保证可见性 因为当 t1 t2 都走到 if(instance==null) 的时候 instance的值被一个线程修改之后 但是 在另一个线程处还是null 所以 使用 volatile 让它回滚 同时 保证有序性

如何保证有序性?
从字节码层上看, new 一个对象 分为3步

  1. 分配空间
  2. 初始化
  3. 引用赋值

但是 在实际的执行过程中 通过即时编译(JIT)、CPU等等,会对我们的字节码指令进行指令重排,即 第二步和第三步的顺序是可以颠倒的 第一步永远是第一步 在单线程是没有影响的

但是 在多线程中,举个例子 有t1和t2两个线程
t1线程 进行132的操作 执行到引用赋值的时候 t1线程结束 t2线程开始 t2线程 进来的时候 判断instance已经有值了 就会直接返回 虽然 instance 返回了客户端 但是 对应的 instance 没有完成初始化 就有可能发送空指针问题

synchronized 保证此时只有一个线程new

class LazySingleton {
    private static volatile LazySingleton instance;//提供一个私有的属性
    private LazySingleton(){

    }//构造方法

    public static LazySingleton getinstance(){
        if(instance==null){
            synchronized (LazySingleton.class){
                instance = new LazySingleton();
            }
        }
        return instance;
    }

}

懒汉模式

基于 JVM 的类加载机制 来保证线程安全的

类加载的 初始化阶段就完成了 实例的初始化。本质上就是借助于jvm的类加载机制,保证实例的唯一性。

类加载过程(jvm会保证 这个类被加载一次):

  1. 加载二进制数据到内存中,生成对应的Class数据结构
  2. 连接:a.验证 ,b.准备(给类的静态成员变量赋默认值),c.解析
  3. 初始化:给类的静态变量赋初值

从IDEA中java文件运行开始→java文件会通过java编译器转换为class文件→class文件由类加载器子系统处理→加载完后JVM对类进行分配处理→最后由jvm与操作系统、硬件交互。

符号引用转为直接引用:对应带方法区的一个内存地址,即直接内存

只有在真正使用对应类的时候,才会触发初始化 如 (当前类是启动类即main函数所在类,直接进行 new 操作,访问静态属性,静态方法,用反射访问类,初始化一个类的子类等)

即 先判断 这个类有没有在内存中加载 如果 没有 就实例化 如果有 就直接用

类加载顺序

父类的静态字段
父类静态代码块
子类静态字段
子类静态代码块
父类成员变量(非静态字段)
父类非静态代码块
父类构造器
子类成员变量
子类非静态代码块
子类构造器
总结优先级:父类静态>子类静态>父类非静态>子类非静态

public class HungrySingletonTest {
    public static void main(String[] args) {
        HungrySingleton a = HungrySingleton.getinstance();
        HungrySingleton b = HungrySingleton.getinstance();
        System.out.println(a==b);
    }
}
class HungrySingleton{
    private static HungrySingleton instance=new HungrySingleton();
    private HungrySingleton(){

    }

    public static HungrySingleton getinstance(){
        return instance;
    }
}

在这里插入图片描述

静态内部类模式

实际上 也就是一个懒汉的实现 不过 是基于jvm的
通过 类加载机制 实现的懒汉
调用 getinner()方法 返回这个值的时候 才会 导致 inner 的初始化

本质上是利用类的加载机制来保证线程安全
只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式

public class InnerClassSingletonTest {
    public static void main(String[] args) {
        new Thread(() -> {
            InnerClassSingleton a = InnerClassSingleton.getinner();
            System.out.println(a);
        }).start();
        new Thread(() -> {
            InnerClassSingleton a = InnerClassSingleton.getinner();
            System.out.println(a);
        }).start();
    }
}
class InnerClassSingleton{
    private static class InnerClass{
        private static InnerClassSingleton inner= new InnerClassSingleton();
    }
    private InnerClassSingleton(){

    }
    public static InnerClassSingleton getinner(){
        return InnerClass.inner;
    }
}

在这里插入图片描述

附加 使用枚举实现

public class LazySingletonTest {
    public static void main(String[] args) {
        new Thread(() -> {
            SingletonObject a = SingletonObject.getInstance();
            System.out.println(a);

        }).start();
        new Thread(() -> {
            SingletonObject a = SingletonObject.getInstance();
            System.out.println(a);

        }).start();
    }



}

class SingletonObject {

    private SingletonObject() {
    }

    private enum Singlet{
        INSTANCE;

        private final SingletonObject instance;


        Singlet() {
            instance=new SingletonObject();
        }

        private SingletonObject getInstance() {
            return instance;
        }
    }

    public static SingletonObject getInstance() {
        return Singlet.INSTANCE.getInstance();
    }
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值