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

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

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

不安全的发布示例代码:

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() 分配对象的内存空间
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使用,就会出现问题。

代码五: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;}}
}

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

从安全和不安全两个角度,教你如何发布对象(含各种单例代码)相关推荐

  1. 殊途同归的两种角度理解岭回归(内含有sklearn例子)

    在学习统计学专业课<回归分析技术>时学过岭回归,学机器学习时也涉及到岭回归,但是两个角度的思想方法略有不同,但最后的结果却是殊途同归的,最近准备统计学考研的复试时,对比了两种思路,觉得很有 ...

  2. 终于有阿里P8从开发、运维两个角度总结出了Redis实战手册

    从开发.运维两个角度总结了Redis实战经验,深入浅出地剖析底层实现,包含大规模集群开发与运维的实际案例.应用技巧. 前言 Redis作为基于键值对的NoSQL数据库,具有高性能.丰富的数据结构.持久 ...

  3. 从两个角度求解线性回归模型参数

    1.基本形式 对含有nnn个属性的样本x=(x1;x2;...;xn)\left ( x_{1} ;x_{2};...;x_{n}\right )(x1​;x2​;...;xn​),线性模型将通过属性 ...

  4. 前端百题斩【032】——两个角度一个实战了解事件循环

    写该系列文章的初衷是"让每位前端工程师掌握高频知识点,为工作助力".这是前端百题斩的第32斩,希望朋友们关注公众号"执鸢者",用知识武装自己的头脑. 111 9 ...

  5. IOS单例的两种实现方式

    单例模式算是开发中比较常见的一种模式了.在iOS中,单例有两种实现方式(至少我目前只发现两种). 根据线程安全的实现来区分,一种是使用@synchronized ,另一种是使用GCD的dispatch ...

  6. 001_支持并发的两次判空懒汉单例

    package com.zr.single;/*** 支持并发的两次判空懒汉单例*/ public class TwiceJudgeNullLazySingleton {private TwiceJu ...

  7. 字少事大|两张表格教你快速选择适合的MCU进行物联网开发

    上图是ST正式发布的涨价通知:2021年1月1日起,全线产品涨价 由于晶圆代工产能持续紧缺,从MCU芯片厂商来看,ST.NXP.Renesas等国外半导体企业都出现了缺货.涨价.交期大幅拉长的情况. ...

  8. 在Hibernate的session中同时有两个相同id的同类型对象,修改失败

    若在Hibernate的session中同时有两个相同id的同类型对象,修改会失败,报错:a different object with the same identifier value was a ...

  9. 前端:JS/32/form对象(表单)(form对象的属性,方法和事件),受返回值影响的两个事件(onclick事件,onsubmit事件),获取表单的元素对象的三种方式,表单的提交和验证方法总结

    form 对象(表单) 一个<form>标记,就是一个<form>对象: 1,form对象的属性 name :表单的名称,主要用来让JS来控制表单: action :表单的数据 ...

最新文章

  1. 力扣(LeetCode)78
  2. 光纤收发器在高清网络视频监控工程项目中的应用
  3. commit git idea 速度慢_关于Git,这篇文章还不够吗?
  4. volatile简记
  5. 解决PLSQL Developer 插入中文 乱码问题
  6. Django组件--cookie与session
  7. 静态/动态注冊广播的差别
  8. 从Chrome源码看JS Array的实现
  9. Codevs3332 数列
  10. 全球各大网站的服务器使用什么操作系统和WEB服务器(转)
  11. 游戏服务器级别分类及对应服务器架构
  12. GBASE 8s 物理日志缓冲区(Physical-log buffer)
  13. fluent当中的梯度宏和VOF梯度的获取【转载】
  14. 微信的常用设备 只能看到android,安卓手机撤回的微信图片可以查看啦,赶紧学起来...
  15. STM32F4系列单片机选型详解
  16. TikTok在国内运营一个月,20万粉丝蛮简单的,绝对避免抖音国际版零播放问题
  17. shell中初始化数组并遍历数组
  18. HTTP抓包神器---Fiddler
  19. 详解JSP 中Spring工作原理及其作用
  20. 基于opencv的SFR算法

热门文章

  1. vim 文本编辑器_标志性的文本编辑器Vim庆祝成立25周年
  2. 开源图书馆系统Evergreen奖励社区
  3. 学习 | egg.js 从入门到精通
  4. Bootstrap 进度条
  5. Bootstrap 支持的设备类型
  6. 修复IE下列表 li 的阶梯Bug
  7. CAN笔记(6) CAN协议(一)
  8. html注释的内容如何修改,如何用自定义元素替换HTML注释
  9. 5 批量更新多条记录_批量更新现有记录的默认表格方法
  10. python数据分类聚类案例_Python实现的KMeans聚类算法实例分析