双重检查锁定模式Java中的陷阱案例

目录
  • 1、简介
  • 2、Java中的双重检查锁定
  • 3、列举方案
    • 3.1 利用 ThreadLocal
    • 3.2 利用volatile(解决重排序问题)
  • 4、总结

1、简介

双重检查锁定(也叫做双重检查锁定优化)是一种软件设计模式

它的作用是减少延迟初始化在多线程环境下获取锁的次数,尤其是单例模式下比较突出。

  • 软件设计模式:解决常用问题的通用解决方案。编程中针对一些常见业务固有的模版。
  • 延迟初始化:在编程中,将对象的创建,值计算或其他昂贵过程延迟到第一次使用时进行。
  • 单例模式:在一定范围内,只生成一个实例对象。

2、Java中的双重检查锁定

单例模式我们需保证实例只初始化一次。

下面例子在单线程环境奏效,多线程环境下会有线程安全问题(instance被初始化多次)。

private static Singleton instance;
public static Singleton getInstance() {
    if (null == instance) {
        instance = new Singleton();
    }
    return instance;
}

下面例子主要是性能问题。首先加锁操作开销很大,因为线程安全发生在对象初始化,而这里做了做了全局控制,造成浪费。

public synchronized static Singleton getInstance() {
    if (null == instance) {
        instance = new Singleton();
    }
    return instance;
}

为了控制线程安全又能保证性能,双重检查锁定模式出现。

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

逻辑如下:

我们分析一下执行逻辑:

假设有三个线程 T1 T2 T3 ,依次访问 getInstance 方法。

  • T1 第一次检查为Null 进入同步块,T1持有锁,第二次检查为Null 执行对象创建。
  • T2 第一次检查为Null 进入同步块,T2等待T1释放锁,锁释放后,T2进入执行第二次检查不为Null,返回实例对象。
  • T3 第一次检查不为Null,直接返回对象。

上面一切似乎很完美,但是这里面存在陷阱。根据Java内存模型我们知道,编译器优化处理会进行重排序。

instance = new Singleton() 大体分两个步骤;

  • 1 创建初始化对象;
  • 2 引用赋值。

而 1 2 步骤可能颠倒,会造成对象属性在初始化前调用的错误。

private static Singleton instance;
...
instance = new Singleton();
...

public class Singleton {
    private int age;
    public Singleton() {
        this.age = 80;
    }
}

这种细微的错误不容易出现,但是它的确存在。大家可以参考下面这份报告,里面详细记录这个问题。

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

3、列举方案

报告里面也列举了几种解决方案

3.1 利用 ThreadLocal

private static final ThreadLocal<Singleton> threadInstance = new ThreadLocal<>();
public static Singleton getInstance() {
    if (null == threadInstance.get()) {
        createInstance();
    }
    return instance;
}
private static void createInstance() {
    synchronized (Singleton.class) {
        if (instance == null)
            instance = new Singleton();
    }
    threadInstance.set(instance);
}

3.2 利用volatile(解决重排序问题)

private volatile static Singleton instance;
public static Singleton getInstance() {
    if (null == instance) {
        synchronized (Singleton.class) {
            if (null == instance) {
                instance = new Singleton();
            }
        }
    }
    return instance;
}

下面是不同方案下的性能比较报告

http://www.cs.umd.edu/~pugh/java/memoryModel/DCL-performance.html

4、总结

本章节主要记录了双重检查锁定模式使用中应该注意的细微事项。

到此这篇关于双重检查锁定模式Java中的陷阱案例的文章就介绍到这了,更多相关双重检查锁定模式Java中的陷阱内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java多线程之死锁详解

    目录 1.死锁 2.死锁经典问题--哲学家就餐问题 总结 1.死锁 出现场景:当线程A拥有了A对象的锁,想要去获取B对象的锁:线程B拥有了B对象的锁,想要拥有A对象的锁,两个线程在获取锁的时候,都不会释放已经持有的锁,于是,就造成了死锁. 示例代码: @Slf4j public class ThreadTest { private static Object objectA = new Object(); private static Object objectB = new Object();

  • 并发编程之Java内存模型锁的内存语义

    目录 1.锁的释放-获取建立的happens-before关系 2.锁释放和获取的内存语义 3.锁内存的语义实现 4.concurrent包的实现 简介: 锁的作用是让临界区互斥执行.本文阐述所得另一个重要知识点--锁的内存语义. 1.锁的释放-获取建立的happens-before关系 锁是Java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息. 锁释放-获取的示例代码: package com.lizba.p1; /** * <p>

  • Java并发编程加锁导致的活跃性问题详解方案

    目录 死锁(Deadlock) 死锁的解决和预防 1.超时释放锁 2.按顺序加锁 3.死锁检测 活锁(Livelock) 避免活锁 饥饿 解决饥饿 性能问题 上下文切换 什么是上下文切换? 减少上下文切换的方法 资源限制 什么是资源限制 资源限制引发的问题 如何解决资源限制的问题 我们主要处理锁带来的问题. 首先就是最出名的死锁 死锁(Deadlock) 什么是死锁 死锁是当线程进入无限期等待状态时发生的情况,因为所请求的锁被另一个线程持有,而另一个线程又等待第一个线程持有的另一个锁 导致互相等

  • java高并发的ReentrantLock重入锁

    目录 synchronized的局限性 ReentrantLock ReentrantLock基本使用 ReentrantLock是可重入锁 ReentrantLock实现公平锁 ReentrantLock获取锁的过程是可中断的 tryLock无参方法 tryLock有参方法 ReentrantLock其他常用的方法 获取锁的4种方法对比 总结 synchronized的局限性 synchronized是java内置的关键字,它提供了一种独占的加锁方式.synchronized的获取和释放锁由j

  • Java多线程之读写锁分离设计模式

    主要完成任务: 1.read read 并行化 2.read write 不允许 3.write write 不允许 public class ReaderWorker extends Thread { private final SharedData data; public ReaderWorker(SharedData data) { this.data = data; } @Override public void run() { while (true) { try { char[]

  • java中synchronized锁的升级过程

    目录 synchronized锁的升级(偏向锁.轻量级锁及重量级锁) java同步锁前置知识点 synchronized同步锁 java对象头 偏向锁 轻量级锁 重量级锁 关于自旋锁 打印偏向锁的参数 synchronized原理解析 一:synchronized原理解析 1:对象头 2:Synchronized在JVM中的实现原理 三.锁的优化 1.锁升级 2.锁粗化 3.锁消除 synchronized锁的升级(偏向锁.轻量级锁及重量级锁) java同步锁前置知识点 1.编码中如果使用锁可以

  • 双重检查锁定模式Java中的陷阱案例

    目录 1.简介 2.Java中的双重检查锁定 3.列举方案 3.1 利用 ThreadLocal 3.2 利用volatile(解决重排序问题) 4.总结 1.简介 双重检查锁定(也叫做双重检查锁定优化)是一种软件设计模式. 它的作用是减少延迟初始化在多线程环境下获取锁的次数,尤其是单例模式下比较突出. 软件设计模式:解决常用问题的通用解决方案.编程中针对一些常见业务固有的模版. 延迟初始化:在编程中,将对象的创建,值计算或其他昂贵过程延迟到第一次使用时进行. 单例模式:在一定范围内,只生成一个

  • java双重检查锁定的实现代码

    在Java程序中,有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化 .这称为延迟初始化或懒加载 看一个不安全的延迟初始化: A线程执行1后,发现对象instance为null,准备对其new,而B线程却先new了,这造成了错误 我们可以利用同步锁,保证正确: 但是对整个方法进行同步开销太大,人们想出了双重检查锁定: 最小范围所用同步锁,利用双重检查看似实现了目的,但这出现了一个问题:当A线程4执行时,线程B的7还未执行完成,而线程A判定instance != n

  • Java中双重检查锁(double checked locking)的正确实现

    目录 前言 加锁 双重检查锁 错误的双重检查锁 隐患 正确的双重检查锁 总结 前言 在实现单例模式时,如果未考虑多线程的情况,就容易写出下面的错误代码: public class Singleton { private static Singleton uniqueSingleton; private Singleton() { } public Singleton getInstance() { if (null == uniqueSingleton) { uniqueSingleton =

  • 浅谈对java中锁的理解

    在并发编程中,经常遇到多个线程访问同一个 共享资源 ,这时候作为开发者必须考虑如何维护数据一致性,在java中synchronized关键字被常用于维护数据一致性.synchronized机制是给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的,因为对于共享资源属性访问是必要也是必须的,下文会有具体示例演示. 一.java中的锁 一般在java中所说的锁就是指的内置锁,每个java对象都可以作为一个实现同步的锁,虽然说在java中一切皆对象, 但是锁

  • Java中单例模式的7种写法

    第一种(懒汉,线程不安全): public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 这种写法lazy loading很明显,但是致命的是在多线程不能正常工作.

  • 详解java中的阻塞队列

    阻塞队列简介 阻塞队列(BlockingQueue)首先是一个支持先进先出的队列,与普通的队列完全相同: 其次是一个支持阻塞操作的队列,即: 当队列满时,会阻塞执行插入操作的线程,直到队列不满. 当队列为空时,会阻塞执行获取操作的线程,直到队列不为空. 阻塞队列用在多线程的场景下,因此阻塞队列使用了锁机制来保证同步,这里使用的可重入锁: 而对于阻塞与唤醒机制则有与锁绑定的Condition实现 应用场景:生产者消费者模式 java中的阻塞队列 java中的阻塞队列根据容量可以分为有界队列和无界队

  • Java中的双重检查(Double-Check)详解

    在 Effecitve Java 一书的第 48 条中提到了双重检查模式,并指出这种模式在 Java 中通常并不适用.该模式的结构如下所示: public Resource getResource() { if (resource == null) { synchronized(this){ if (resource==null) { resource = new Resource(); } } } return resource; } 该模式是对下面的代码改进: public synchron

  • JAVA中常用的设计模式:单例模式,工厂模式,观察者模式

    1.单例模式 每个类只能创建一个实例对象 Java Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在. 使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection). 好处: 第一.控制资源的使用,通过线程同步来控制资源的并发访问: 第二.控制实例产生的数量,达到节约资源的目的. 第三.作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程

  • Java中的常量避免反模式的方法

    在应用中,我们往往需要一个常量文件,用于存储被多个地方引用的共享常量.在设计应用时,我也遇到了类似的情况,很多地方都需要各种各样的常量. 我确定需要一个单独的文件来存储这些静态公共常量.但是我不是特别确定是应该用接口还是类(枚举不满足我的需求).我有两种选择: 使用接口,如: package one; public interface Constants { String NAME="name1"; int MAX_VAL=25; } 或 package two; public cla

  • Java中SpringSecurity密码错误5次锁定用户的实现方法

    Spring Security简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作. 下面看下实例代码: 第

随机推荐