Java并发编程学习系列八:单例模式

简介: 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设计模式(一)—— 单例模式

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

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


目录
相关文章
|
1天前
|
存储 安全 Java
Java并发编程中的高效数据结构:ConcurrentHashMap解析
【4月更文挑战第25天】在多线程环境下,高效的数据访问和管理是至关重要的。Java提供了多种并发集合来处理这种情境,其中ConcurrentHashMap是最广泛使用的一个。本文将深入分析ConcurrentHashMap的内部工作原理、性能特点以及它如何在保证线程安全的同时提供高并发性,最后将展示其在实际开发中的应用示例。
|
2天前
|
Java API 调度
[Java并发基础]多进程编程
[Java并发基础]多进程编程
|
2天前
|
Java API 调度
[AIGC] 深入理解Java并发编程:从入门到进阶
[AIGC] 深入理解Java并发编程:从入门到进阶
|
2天前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
|
2天前
|
前端开发 Java 测试技术
Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
|
2天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
2天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句
Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句
|
2天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
ava从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
|
2天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
2天前
|
Java API
Java从入门到精通:2.1.5深入学习Java核心技术之文件操作
Java从入门到精通:2.1.5深入学习Java核心技术之文件操作