hao同学的技术博客

  • 首页
  • Java
    • Java
    • JVM教程
    • Java面试
    • Java并发入门
    • Java并发进阶
  • 项目
    • 从零打造项目
  • Python
    • Python
    • Python爬虫
    • 算法
  • Java框架
    • Spring
    • SpringBoot
  • 前端
    • Angular
  • 其他
    • Linux
    • SQL
  • 随笔
分享技术,记录人生
一个痴迷于技术的厨艺爱好者
  1. 首页
  2. Java并发入门
  3. 正文

Java并发编程入门学习之单例模式

2022年5月28日 325点热度 0人点赞 0条评论

Java并发编程入门学习之单例模式插图

介绍

什么是单例模式?

通俗的讲,就是在应用程序中只需要某个类保留唯一一个实例对象,不希望有更多的实例。单例模式是 Java 设计模式中最简单的设计模式之一,在应用程序中经常被用到。

应用场景

单例模式的应用场景有很多,比如线程池、日志对象、缓存、数据库连接池、计算机系统设备管理器等等。这些常常都设计成全局唯一的,方便集中管理,也节省系统的开销。

实现方式

实现单例模式要注意以下三点:

1、单例类只能有一个实例,不能从其他对象中 new 出来, 即构造器用 private 修饰。
2、单例类必须自己创建自己的唯一实例,需要实现一个方法提供这个实例。
3、单例类必须能给其他对象提供这一实例。

接下来我们讲讲在 Java 中如何实现单例模式 :

(1)饿汉式

饿汉式,顾名思义指的是在类加载的时候就初始化好对象,不管有没有用到。绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题。

Spring 中 IOC 容器 ApplicationContext 就是典型的饿汉式单例。

public class Singleton {

    private final static Singleton singleton = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return singleton;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Singleton singleton1 = Singleton.getInstance();   //获取都是同一个对象
            System.out.println(singleton1.hashCode());      
        }
    }
}

还有另外一种写法,利用静态代码块的机制:

public class Singleton {
    // 1. 私有化构造器
    private Singleton(){}

    // 2. 实例变量
    private static final Singleton instance;

    // 3. 在静态代码块中实例化
    static {
        instance = new Singleton();
    }

    // 4. 提供获取实例方法
    public static Singleton getInstance(){
        return instance;
    }
}

(2)懒汉式

懒汉式和饿汉式相对,指的在程序加载时不初始化对象,什么时候被引用什么时候才初始化对象,即在第一次使用的时候才去初始化对象,可以避免内存浪费。注意在获取实例的 getInstance()方法前加上了 synchronized 关键字,这是为了保证线程安全,避免多线程同一时刻获取对象时造成生成了多个实例。

public class Singleton {

    private static Singleton singleton = null;

    private Singleton(){}

    public synchronized static Singleton getInstance(){
        if(singleton == null){   // 1
            singleton = new Singleton(); // 2
        }
        return singleton;
    }

}

(3)双重检查锁

双重检查锁是在懒汉式基础上演变过来的,当分析懒汉式代码时,你会发现只有在第一次调用获取实例方法时才需要同步。因为仅步骤2处的代码需要同步,但只有第一次调用才执行此行,后面的其他调用没有执行此行,但都付出了同步的代价。因此为避免在实例已经创建的情况下每次获取实例都加锁取,提高效率,双重检查锁应运而生。

为什么要二次检查?分析双重检查锁代码,多线程并发情况下, 第一个线程执行完 synchronized 的代码块后,后面的线程仍然需要对 singleton 进行第二次检查,即步骤3,避免重复实例化对象。所以需要对实例对象做两次检查。

既然 synchronized 能保证有序性,为什么还要加 volatile?多线程情况下,synchronized 关键字能够起到同步的作用,保证每次只有一个线程能够操作。这样一来对于其内部就相当于单线程操作,但是不会影响其内部的指令重排。我们知道 volatile 可以禁止指令重排,步骤4是属于复合操作指令,首先是实例化对象,然后才是写操作,关于实例化对象其实分为以下三个步骤:

​ (1)分配内存空间。

​ (2)初始化对象。

​ (3)将内存空间的地址赋值给对应的引用。

但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:

​ (1)分配内存空间。

​ (2)将内存空间的地址赋值给对应的引用。

​ (3)初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为 volatile 类型的变量。volatile 变量的写操作,会保证之前的所有指令一定会在 volatile 写操作之前完成,那么 instance = new SingleTon() 这个复合操作指令一定是对象创建完成再进行赋值。

public class Singleton {

    private volatile static Singleton singleton = null;

    private Singleton(){}

    public static Singleton getInstance(){
        if(singleton == null){ // 步骤1
            synchronized (Singleton.class){ // 步骤2
                if(singleton == null){ // 步骤3
                    singleton = new Singleton(); // 步骤4
                }
            }
        }
        return singleton;
    }

}

(4)静态内部类

这种方式能达到双检锁方式一样的功效,但实现更为简单。这种和饿汉式比较,在类加载时,singleton实例并没有被初始化,需要显示调用getInstance()方法才会转载SingleHolder类,从而初始化singleton实例,所以达到了延时加载的效果。此方法在实际使用中用的最多,推荐此种写法。

public class Singleton {

    private static class SingleHolder{
        private static Singleton singleton = new Singleton();
    }

    private Singleton(){ }

    public static Singleton getInstance(){
        return SingleHolder.singleton;
    }

}

(5)枚举

这种方式巧妙的应用了枚举的特点,构造器本身私有,写法简单,自动支持序列化机制,防止多次实例化,获取实例可以通过 Singleton.INSTANCE 来访问。

public enum Singleton {

    INSTANCE;
}

参考文献

java 在同步锁内外判断两次,有什么用处?

Java设计模式(一)—— 单例模式

设计模式 - 单例模式(详解)看看和你理解的是否一样?

设计模式 - 单例模式之多线程调试与破坏单例

本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可
标签: Java 并发编程
最后更新:2022年5月28日

hresh

这是一个专注于IT技术学习交流的个人技术博客网站,包括Java学习、Python爬虫、Web开发实践等领域,深耕Java领域,内容涵盖Java基础、Java并发编程、Java虚拟机、Java面试等核心知识点。

点赞
< 上一篇
下一篇 >

文章评论

取消回复

hresh

这是一个专注于IT技术学习交流的个人技术博客网站,包括Java学习、Python爬虫、Web开发实践等领域,深耕Java领域,内容涵盖Java基础、Java并发编程、Java虚拟机、Java面试等核心知识点。

文章目录
  • 介绍
  • 应用场景
  • 实现方式
  • 参考文献
最新 热点 随机
最新 热点 随机
后端必知:遵循Google Java规范并引入checkstyle检查 Spring Security结合Redis实现缓存功能 Spring Security结合JWT实现认证与授权 Spring Security自定义认证逻辑实现图片验证码登录 Spring Security进阶学习 Spring Security入门学习
SpringBoot集成Spring Data JPA项目实操 Spring Security进阶学习 SpringBoot结合XXL-JOB实现定时任务 关于即时编译器的优化措施 Python 求解最小公倍数 Spring IoC资源管理之ResourceLoader

COPYRIGHT © 2022 hao同学的技术博客. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

鄂ICP备2022007381号

鄂公网安备 42010302002449号