代码实例解读如何安全发布对象

代码,实例,解读,如何,安全,发布,对象 · 浏览次数 : 51

小编点评

**内容摘要** 本文包含以下内容: * SingletonExample5懒汉模式 (双重锁同步锁单例模式) * SingletonExample6饿汉模式 * SingletonExample7枚举方式进行实例化

正文

摘要:在高并发环境下如何安全的发布对象实例。

本文分享自华为云社区《【高并发】如何安全的发布对象(含各种单例代码分析)》,作者:冰 河。

今天,为大家带来一篇有技术含量的文章,那就是在高并发环境下如何安全的发布对象实例。

发布对象:使一个对象能够被当前范围之外的代码所使用
对象溢出:是一种错误的发布,当一个对象还没有构造完成时,就使它被其他线程所见

不安全的发布示例代码:

package io.binghe.concurrency.example.publish;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
@Slf4j
public class UnsafePublish {
 private String[] states = {"a", "b", "c"};
 public String[] getStates(){
 return states;
    }
 public static void main(String[] args){
 UnsafePublish unsafePublish = new UnsafePublish();
 log.info("{}", Arrays.toString(unsafePublish.getStates()));
 unsafePublish.getStates()[0] = "d";
 log.info("{}", Arrays.toString(unsafePublish.getStates()));
    }
}

其中,每个线程都能获取到UnsafePublish类的私有成员变量states,并修改states数组的元素值,造成其他线程获取的states元素值不确定。

对象溢出示例代码:

package io.binghe.concurrency.example.publish;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Escape {
 private int thisCanBeEscape = 0;
 public Escape(){
 new InnerClass();
    }
 private class InnerClass{
 public InnerClass(){
 log.info("{}", Escape.this.thisCanBeEscape);
        }
    }
 public static void main(String[] args){
 new Escape();
    }
}

其中,内部类InnerClass的构造方法中包含了对封装实例Escape的隐含的引用(体现在InnerClass的构造方法中引用了Escape.this),在对象没有被正确构造完成之前,就会被发布,有可能存在不安全的因素。

一个导致this在构造期间溢出的错误:在构造函数中,启动一个线程,无论是隐式的启动还是显式的启动,都会造成this引用的溢出(因为新线程总是在所属对象构造完毕之前就已经看到this引用了)。所以,如果要在构造函数中创建线程,则不要在构造函数中启动线程,可以使用一个专有的start()方法或者一个初始化方法,来统一启动线程,可以采用工厂方法和私有构造函数来完成对象的创建和监听器的注册,之后统一启动线程,来避免溢出。

注意:在对象未构造完成之前,不可以将其发布

如何安全的发布对象:

(1)在静态初始化函数中初始化一个对象引用
(2)将对象的引用保存到volatile类型域或者AtomicReference对象中
(3)将对象的引用保存到某个正确构造对象的final类型域中
(4)将对象的引用保存到一个由锁保护的域中

接下来,看几个单例对象的示例代码,其中有些代码是线程安全的,有些则不是线程安全的,需要大家细细品味,这些代码也是冰河本人在高并发环境下测试验证过的。

代码一:SingletonExample1

这个类是懒汉模式,并且是线程不安全的

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程不安全的
 */
public class SingletonExample1 {
 private SingletonExample1(){}
 private static SingletonExample1 instance = null;
 public static SingletonExample1 getInstance(){
 //多个线程同时调用,可能会创建多个对象
 if (instance == null){
            instance = new SingletonExample1();
        }
 return instance;
    }
}

代码二:SingletonExample2

饿汉模式,单例实例在类装载的时候进行创建,是线程安全的

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 饿汉模式,单例实例在类装载的时候进行创建,是线程安全的
 */
public class SingletonExample2 {
 private SingletonExample2(){}
 private static SingletonExample2 instance = new SingletonExample2();
 public static SingletonExample2 getInstance(){
 return instance;
    }
}

代码三:SingletonExample3

懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程安全的,但是这个写法不推荐

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程安全的,但是这个写法不推荐
 */
public class SingletonExample3 {
 private SingletonExample3(){}
 private static SingletonExample3 instance = null;
 public static synchronized SingletonExample3 getInstance(){
 if (instance == null){
            instance = new SingletonExample3();
        }
 return instance;
    }
}

代码四:SingletonExample4

懒汉模式(双重锁同步锁单例模式),单例实例在第一次使用的时候进行创建,但是,这个类不是线程安全的!!!!!

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 懒汉模式(双重锁同步锁单例模式)
 *              单例实例在第一次使用的时候进行创建,这个类不是线程安全的
 */
public class SingletonExample4 {
 private SingletonExample4(){}
 private static SingletonExample4 instance = null;
 //线程不安全
 //当执行instance = new SingletonExample4();这行代码时,CPU会执行如下指令:
 //1.memory = allocate() 分配对象的内存空间
 //2.ctorInstance() 初始化对象
 //3.instance = memory 设置instance指向刚分配的内存
 //单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序。
 // 指令重排序对单线程没有影响,单线程下CPU可以按照顺序执行以上三个步骤,但是在多线程下,如果发生了指令重排序,则会打乱上面的三个步骤。
 //如果发生了JVM和CPU优化,发生重排序时,可能会按照下面的顺序执行:
 //1.memory = allocate() 分配对象的内存空间
 //3.instance = memory 设置instance指向刚分配的内存
 //2.ctorInstance() 初始化对象
 //假设目前有两个线程A和B同时执行getInstance()方法,A线程执行到instance = new SingletonExample4(); B线程刚执行到第一个 if (instance == null){处,
 //如果按照1.3.2的顺序,假设线程A执行到3.instance = memory 设置instance指向刚分配的内存,此时,线程B判断instance已经有值,就会直接return instance;
 //而实际上,线程A还未执行2.ctorInstance() 初始化对象,也就是说线程B拿到的instance对象还未进行初始化,这个未初始化的instance对象一旦被线程B使用,就会出现问题。
 public static SingletonExample4 getInstance(){
 if (instance == null){
 synchronized (SingletonExample4.class){
 if(instance == null){
                    instance = new SingletonExample4();
                }
            }
        }
 return instance;
    }
}

线程不安全分析如下:

当执行instance = new SingletonExample4();这行代码时,CPU会执行如下指令:

1.memory = allocate() 分配对象的内存空间
2.ctorInstance() 初始化对象
3.instance = memory 设置instance指向刚分配的内存

单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序。

指令重排序对单线程没有影响,单线程下CPU可以按照顺序执行以上三个步骤,但是在多线程下,如果发生了指令重排序,则会打乱上面的三个步骤。

如果发生了JVM和CPU优化,发生重排序时,可能会按照下面的顺序执行:

1.memory = allocate() 分配对象的内存空间
2.instance = memory 设置instance指向刚分配的内存
3.ctorInstance() 初始化对象

假设目前有两个线程A和B同时执行getInstance()方法,A线程执行到instance = new SingletonExample4(); B线程刚执行到第一个 if (instance == null){处,如果按照1.3.2的顺序,假设线程A执行到3.instance = memory 设置instance指向刚分配的内存,此时,线程B判断instance已经有值,就会直接return instance;而实际上,线程A还未执行2.ctorInstance() 初始化对象,也就是说线程B拿到的instance对象还未进行初始化,这个未初始化的instance对象一旦被线程B使用,就会出现问题。

代码五:SingletonExample5

懒汉模式(双重锁同步锁单例模式)单例实例在第一次使用的时候进行创建,这个类是线程安全的,使用的是 volatile + 双重检测机制来禁止指令重排达到线程安全

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 懒汉模式(双重锁同步锁单例模式)
 *              单例实例在第一次使用的时候进行创建,这个类是线程安全的
 */
public class SingletonExample5 {
 private SingletonExample5(){}
 //单例对象  volatile + 双重检测机制来禁止指令重排
 private volatile static SingletonExample5 instance = null;
 public static SingletonExample5 getInstance(){
 if (instance == null){
 synchronized (SingletonExample5.class){
 if(instance == null){
                    instance = new SingletonExample5();
                }
            }
        }
 return instance;
    }
}

代码六:SingletonExample6

饿汉模式,单例实例在类装载的时候(使用静态代码块)进行创建,是线程安全的

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 饿汉模式,单例实例在类装载的时候进行创建,是线程安全的
 */
public class SingletonExample6 {
 private SingletonExample6(){}
 private static SingletonExample6 instance = null;
 static {
        instance = new SingletonExample6();
    }
 public static SingletonExample6 getInstance(){
 return instance;
    }
}

代码七:SingletonExample7

枚举方式进行实例化,是线程安全的,此种方式也是线程最安全的

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 枚举方式进行实例化,是线程安全的,此种方式也是线程最安全的
 */
public class SingletonExample7 {
 private SingletonExample7(){}
 public static SingletonExample7 getInstance(){
 return Singleton.INSTANCE.getInstance();
    }
 private enum Singleton{
        INSTANCE;
 private SingletonExample7 singleton;
 //JVM保证这个方法绝对只调用一次
 Singleton(){
            singleton = new SingletonExample7();
        }
 public SingletonExample7 getInstance(){
 return singleton;
        }
    }
}

 

点击关注,第一时间了解华为云新鲜技术~

与代码实例解读如何安全发布对象相似的内容:

代码实例解读如何安全发布对象

摘要:在高并发环境下如何安全的发布对象实例。 本文分享自华为云社区《【高并发】如何安全的发布对象(含各种单例代码分析)》,作者:冰 河。 今天,为大家带来一篇有技术含量的文章,那就是在高并发环境下如何安全的发布对象实例。 发布对象:使一个对象能够被当前范围之外的代码所使用对象溢出:是一种错误的发布,

利用ChatGPT提升测试工作效率——测试工程师的新利器(一)

在测试工作中可以辅助功能测试包括需求分析或解读代码(注意代码安全)后生成测试用例,还可以辅助生成代码,接口测试用例,自动化脚本等各个方向起作用。当然实际使用中可能会因为提示词的不同生成的结果需要人工多次对话训练才可以。但是使用chatGPT肯定比不用能提高工作效率。当然具体落地后如何进行量化提效抽象...

从工具到实践:如何在GitHub上保障开源项目安全?

1998年,Christine Peterson创造了 “开源软件”这个词。她解释道:“这是刻意为之,为了让其他人更容易理解这个领域”。同年,O’Reilly组织了首届“开源峰会”。 开源软件受到更多人青睐原因在于,用户对软件拥有更多的控制权因为他们可以检查代码。对于长期项目来说,开源软件被认为是稳

第134篇:解决浏览器的CORS跨域问题(CORS policy: Cross origin requests are only supported for protocol schemes: http, data, isolated-app, chrome-extension, chrome-untrusted, https, edge.)

好家伙, 我继续尝试着将我的飞机大战使用ES6模块化分离开来,出了点问题 1.出现问题: edge,chrome等一系列浏览器,会为了安全,禁止你跨域访问 目录如下: 主程序 index.html main_1.js main.js 完整代码如下: 1 /* //plane封装成类 2 //实例化后

吐血整理如何在Google Earth Engine上写循环 五个代码实例详细拆解

在这里同步一篇本人的原创文章。原文发布于2023年发布在知乎专栏,转移过来时略有修改。全文共计3万余字,希望帮助到GEE小白快速进阶。 引言 这篇文章主要解答GEE中.map()和.iterate()函数的用法。 首先解答一个疑问,为什么需要自己写循环?确实,GEE 为各种数据类型提供了无数常用的内

Java静态变量在静态方法内部无法改变值

一、如何解决“Java静态变量在静态方法内部无法改变值”的问题 在Java中,静态变量(也称为类变量)属于类本身,而不是类的任何特定实例。它们可以在没有创建类的实例的情况下访问和修改。如果我们发现在静态方法内部无法改变静态变量的值,这通常是因为我们的代码中有一些逻辑错误或误解。 下面是一个简单的示例

Nuxt3 的生命周期和钩子函数(六)

摘要:本文深入解析了Nuxt3框架中的多个核心生命周期钩子和组件注册功能,包括imports:sources、imports:extend、imports:context、imports:dirs、components:dirs及components:extend,通过实例代码指导开发者如何在不同场...

Nuxt3 的生命周期和钩子函数(四)

概述了Nuxt3的六个关键生命周期钩子用途:modules:before至build:before,指导如何在应用初始化、模块管理、配置解析、模板处理及构建前执行自定义操作,附带实例代码,强化Nuxt应用的灵活性和可控性。

驱动开发:内核解析PE结构节表

在笔者上一篇文章`《驱动开发:内核解析PE结构导出表》`介绍了如何解析内存导出表结构,本章将继续延申实现解析PE结构的PE头,PE节表等数据,总体而言内核中解析PE结构与应用层没什么不同,在上一篇文章中`LyShark`封装实现了`KernelMapFile()`内存映射函数,在之后的章节中这个函数会被多次用到,为了减少代码冗余,后期文章只列出重要部分,读者可以自行去前面的文章中寻找特定的片段。

Python日志模块:实战应用与最佳实践

**本文详细解析了Python的logging模块,从基本介绍到实际应用和最佳实践。我们通过具体的代码示例解释了如何高效地使用这个模块进行日志记录,以及如何避免常见的陷阱,旨在帮助读者更好地掌握这个强大的工具。** ![file](https://img2023.cnblogs.com/other/