创建对象中的单例模式

单例模式

简介

单例模式是oop(面向对象编程)语言的一种概念 顾名思义 就是一个类只能有一个实例化对象

单例模式分为两种:1 懒汉式加载 2 饿汉式加载 他们又分别有传统实现和优化的推荐实现

spring框架创建对象就是单例模式

懒汉式单例

特点;当需要使用对象的时候才进行实例化。

由于可能有多个线程同时使用对象 因此需要考虑线程安全问题,防止并发访时生成多个实例。

通常使用加锁来解决冲突 是用时间换空间的方案

传统实现代码:

java 复制代码
public class Singleton {
//    设置构造方法
    private Singleton(){};
//    声明一个Singleton对象为obj
    private static Singleton obj;
//    加锁保证obj只能实例化一次 时间换空间
    public static synchronized Singleton getInstance(){
        if (obj==null){
            obj=new Singleton();
        }
        return obj;
    }
}

传统实现方法中,每次获取实例都要被synchronized关键字串行化 即使已经生成了对象实例

而我们加锁的目的是为了防止生成多个实例,因此其实只需要对生成实例的代码加锁,生成实例后,可支持并发访问,提高性能

优化后代码

java 复制代码
package com.xsy.lx.service.entity;

public class Singleton1 {
    //设置私有构造方法
    private Singleton1(){}
    // 最后解释volatile关键字
    private  volatile static Singleton1  obj;
    //获取实例对象的方法
    public static Singleton1  getInstance(){
        //如果已有实例则直接返回,不走锁
        if(obj==null){
            //仅在没生成实例时加锁控制,使并发访问串行化
            synchronized(Singleton.class){
                //多个线程会按序执行到此处,需要再次检查是否已经实例化
                if(obj==null){
                    obj = new Singleton1();
                }
            }
        }
        return  obj;
    }
}

优化实现 由于检查了两次对象是否已经实例化,因此该方法又被称为"双检发" 能够保证线程安全的同时提升对象实例化后的调用性能

饿汉式单例

饿汉单例的特点是:类加载时便实例化

能够在第一时间实例化对象供其他方法使用 是拿空间换时间的方案

传统代码实现

java 复制代码
package com.xsy.lx.service.entity;

public class Singleton3 {

    private Singleton3(){};
    private static Singleton3 obj=new Singleton3();

    public static Singleton3 getintance(){
        return obj;
    }
}

通过static关键字 在类加载时创建对象

优化实现:

上述传统方法中,由于类加载时就实例化对象,因此当我们调用这个类的其他静态方法时,也会触发类加载器,从而实例化单例独享,会导致空间的暂时浪费。

由于静态内部类中的对象不会默认加载,直到调用了获取该内部类属性的方法 因此可用静态内部类封装实例变量

java 复制代码
package com.xsy.lx.service.entity;

public class Singleton4 {
    // 私有构造函数
    private Singleton4() {}

    // 静态内部类
    private static class SingletonHolder {
        private static Singleton4 instance = new Singleton4();
    }
    public static Singleton4 getInstance(){
        return SingletonHolder.instance;
    }
}

上述两种实现方法中,最推荐这种优化后的饿汉式实现,利用static保证线程安全,利用静态内部类节约了空间,实现了lazy-loading(懒加载),而且代码非常简短,可谓是一箭三雕。

单例模式细节深入分析

设计模式是开发岗位中的高频考点,在设计模式中,单例模式是基本必考的

为什么面试官爱问单例模式?原因如下:

1单例模式是我们平时编程中最常用的设计模式之一

2 单例模式实现简单,不会占用太多面试时间

3 单例模式的实现有很多种 而且有优劣之分 能够体现面试者的基本功和水平

虽然懒汉式单例优化版的实现代码相对复杂 但是有一些细节值得学习和考究

比如这行声明实例的代码中为什么要使用volatile关键字?

java 复制代码
private volatile static Singleton obj;

如果不添加的话 可能出现获取实例为null的情况!

因为使用new来创建对象不是一个原子操作(不可分割的操作序列,要么都成功,要么都失败),而是会被编译成如下三条指令:

  1. 给实例分配内存
  2. 初始化实例的构造
  3. 将实际对象指向分配的内存空间(此时实例应该已经不为空)

正常的思路是123一定按顺序执行。

但事实上,Java会对进行指令重排序。

JVM根据处理器的特性,充分利用多级缓存,多核等进行适当的指令重排序,使程序在保证业务运行的同时,充分利用CPU的执行特点,最大的发挥机器的性能。

即JVM虚拟机在执行上面三条指令时,可能按照132的顺序执行。

假设当13执行完,2还未执行时,如果另外一个线程调用getInstance(),会在判断对象是否为null时返回false(因为3已执行,对象指向了内存空间,已不为空),然后直接返回实例。但由于此时2还没执行,实例并未完全初始化,只是分配了内存空间,就会导致使用对象时出现错误(引用逃逸)。

注意:final字段不能保证初始化过程中的可见性,也无法禁止指令重排序!

而voliate关键字可以通过内存屏障禁止指令重排序,保证创建对象时的123步骤按顺序执行,从而解决上述问题。

相关推荐
谁似人间西林客1 天前
工业大数据实战:看中国智造如何用数据驱动效率革命
大数据·单例模式
张小姐的猫1 天前
【Linux】多线程 —— 线程池 | 单例模式 | 常见锁
linux·运维·服务器·c++·单例模式·设计模式·策略模式
Java面试题总结2 天前
双重检验锁的单例模式在高并发下的可见性问题
单例模式
珊瑚里的鱼5 天前
手撕单例模式中的饿汉模式和懒汉模式,懒汉模式还要再多加一个C++11版本的
开发语言·c++·单例模式
韩曙亮5 天前
【Flutter】Dart 单例 ( 单例模式核心规则 | 饿汉式单例 | 懒汉式单例 | 极简空安全 懒汉式单例 | 工厂构造函数单例 )
flutter·单例模式·dart·饿汉式单例·懒汉式单例·空安全·空赋值
wunaiqiezixin7 天前
如何在C++中实现一个单例模式?
c++·单例模式
basketball6167 天前
设计模式入门:1. 单例模式详解 C++实现
c++·单例模式·设计模式
bugcome_com7 天前
阿里云OSS工具类完整设计与实现:基于.NET的静态单例模式实践
阿里云·单例模式·.net·oss
JAVA9658 天前
JAVA面试-并发篇 03-使用synchronized doublecheck实现单例有什么坑
java·单例模式·面试