文章目录

  • 线程安全性文章索引
  • 脑图
  • 线程安全性的定义
  • 线程安全性的体现
  • 原子性
    • 使用AtomicInteger改造线程不安全的变量
    • incrementAndGet源码分析-UnSafe类 compareAndSwapInt (CAS)
    • CAS操作中可能会带来的ABA问题
      • ABA问题的解决办法
      • AtomicLong 和 LongAdder
      • LongAdder的优化思路
      • LongAdder的优缺点
    • AtomicReference 和 AtomicIntegerFieldUpdater
  • 代码

线程安全性文章索引

并发编程-03线程安全性之原子性(Atomic包)及原理分析

并发编程-04线程安全性之原子性Atomic包的4种类型详解

并发编程-05线程安全性之原子性【锁之synchronized】

并发编程-06线程安全性之可见性 (synchronized + volatile)

并发编程-07线程安全性之有序性


脑图


线程安全性的定义

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何进行交替,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现正确的行为,那么这个类就是线程安全的。


线程安全性的体现

线程安全性主要体现在一下三个方面

1. 原子性
2. 可见性
3. 有序性

原子性:提供互斥访问,同一时刻只能有一个线程来对它进行操作

可见性:一个线程对主内存的修改可以及时被其他线程观察到

有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序

我们逐个来看下这3个特性,首先来学习下线程安全的原子性JDK中提供的类以及原理。


原子性

提到原子性,就不得不提jdk1.5开始提供的juc中的Atomic包,Atomic包中的原子操作类提供了一种用法简单、性能高效、线程安全的更新一个变量的方式。

先回顾下线程不安全的写法


使用AtomicInteger改造线程不安全的变量

下面我们通过示例来演示下Atomic包中的原子类是如何线程安全的更新一个变量的方式


incrementAndGet源码分析-UnSafe类 compareAndSwapInt (CAS)

AtomicInteger#incrementAndGet 是如何实现线程安全的呢?

看下源码 (JDK1.8

调用了Unsafe类中的getAndAddInt()方法,该方法执行一个CAS操作,保证线程安全

UnSafe的getAndAddInt方法实现:

//Unsafe类中的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {        int v;        do {            v = getIntVolatile(o, offset);        } while (!compareAndSwapInt(o, offset, v, v + delta));        return v;
}

getAndAddInt通过一个while循环不断的重试更新要设置的值,直到成功为止,调用的是Unsafe类中的compareAndSwapInt方法,是一个CAS操作方法。 【CAS操作是基于系统原语的(原语的执行必须是连续的,操作期间不会被系统中断,是一条CPU的原子指令),因此是一个不需要加锁的锁,也因此不可能出现死锁的情况。】

CAS方法的主要实现逻辑

CAS:Compare and Swap
CAS(V, E, N)

V:要更新的变量
E:预期值
N:新值

如果V值等于E值,则将V值设为N值;如果V值不等于E值,说明其他线程做了更新,那么当前线程什么也不做。(放弃操作或重新读取数据)

在JDK中的实现为,加入了个偏移量offset

Unsafe里的CAS 操作相关:

//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

上面的incrementAndGet 源码分析是基于JDK1.8的,如果是1.8之前的方法,1.8之前的方法则是通过for的死循环实现的:

//JDK 1.7的源码,由for的死循环实现,并且直接在AtomicInteger实现该方法,
//JDK1.8后,该方法实现已移动到Unsafe类中,直接调用getAndAddInt方法即可
public final int incrementAndGet() {    for (;;) {        int current = get();        int next = current + 1;        if (compareAndSet(current, next))            return next;    }
}

CAS操作中可能会带来的ABA问题

当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值。


ABA问题的解决办法

  • AtomicStampedReference类 时间戳

一个带有时间戳的对象引用,每次修改时,不但会设置新的值,还会记录修改时间。在下一次更新时,不但会对比当前值和期望值,还会对比当前时间和期望值对应的修改时间,只有二者都相同,才会做出更新。

底层实现为:一个键值对Pair存储数据和时间戳,并构造volatile修饰的私有实例;两者都符合预期才会调用Unsafe的compareAndSwapObject方法执行数值和时间戳替换


  • AtomicMarkableReference类

一个boolean值的标识,true和false两种切换状态表示是否被修改。不推荐使用。

原理参考:Java中的CAS和Unsafe类


AtomicLong 和 LongAdder

上面的AtomicInteger也可以改成AtomicLong ,其他地方都无需调整,效果是一样的。 这里我们要引出的JDK8中对AtomicLong的改进类LongAdder.

多次执行,结果总是10000.


LongAdder的优化思路

LongAdder所使用的思想就是热点分离,这一点可以类比一下ConcurrentHashMap的设计思想。就是将value值分离成一个数组,当多线程访问时,通过hash算法映射到其中的一个数字进行计数。而最终的结果,就是这些数组的求和累加。这样一来,就减小了锁的粒度


LongAdder的优缺点

优点:

  • LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。 如果仅仅是需要做形如count++的操作,如果使用的JDK8的话,推荐使用LongAdder代替AtomicLong。

缺点:

  • LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。

AtomicReference 和 AtomicIntegerFieldUpdater

因篇幅原因 AtomicReference 和 AtomicIntegerFieldUpdater 的使用见另外一篇博客
并发编程-04线程安全性之原子性Atomic包详解

原子更新引用类型: AtomicReference

原子更新字段类型: AtomicIntegerFieldUpdater


代码

https://github.com/yangshangwei/ConcurrencyMaster

并发编程-03线程安全性之原子性(Atomic包)及原理分析相关推荐

  1. 并发编程-04线程安全性之原子性Atomic包的4种类型详解

    文章目录 线程安全性文章索引 脑图 概述 原子更新基本类型 Demo AtomicBoolean 场景举例 原子更新数组 Demo 原子更新引用类型 Demo 原子更新字段类型 使用注意事项: Dem ...

  2. 并发编程-05线程安全性之原子性【锁之synchronized】

    文章目录 线程安全性文章索引 脑图 概述 原子性synchronized 修饰的4种对象 修饰代码块 作用范围及作用对象 Demo 多线程下 同一对象的调用 多线程下不同对象的调用 修饰方法 作用范围 ...

  3. 并发编程-06线程安全性之可见性 (synchronized + volatile)

    文章目录 线程安全性文章索引 脑图 可见性定义 导致不可见的原因 可见性 -synchronized (既保证原子性又保证可见性) 可见性 - volatile(但不保证操作的原子性) volatil ...

  4. java 线程安全list_JAVA并发编程实战-线程安全性

    线程安全性: 对象的状态是指存储在状态变量(例如实例和静态域)中的数据. 对象的状态可能包括其他依赖对象的域. 例如:某个HashMap的状态不仅存储在HashMap对象本身,还存储在许多Map.En ...

  5. Java并发编程之线程安全性分析之原子性、可见性、有序性

    一:线程的安全性分析 如何理解线程安全: 当多个线程访问某个共享对象时,不管运行环境采用何种调度方式,或者这些线程如何交替执行,并且主调代码中不需要任何的额外同步操作或者协同操作,这个类都能表现出正确 ...

  6. 并发编程 (三) 线程安全性之避免线程不安全

    文章目录 一.如何保证线程安全 1.1.并发原子类 1.2.加锁机制 1.3.信号量同步共享数据 1.4.封闭线程,不共享数据 1.5.发布与逸出 二.设计线程安全的类 2.1.前提要素 2.2.无限 ...

  7. java并发编程与线程安全

    2019独角兽企业重金招聘Python工程师标准>>> 什么是线程安全 如果对象的状态变量(对象的实例域.静态域)具有可变性,那么当该对象被多个线程共享时就的考虑线程安全性的问题,否 ...

  8. 并发编程-12线程安全策略之常见的线程不安全类

    文章目录 脑图 概述 字符串拼接子之StringBuilder.StringBuffer StringBuilder (线程不安全) StringBuffer (线程安全) 小结 时间相关的类 Sim ...

  9. JAVA并发编程3_线程同步之synchronized关键字

    在上一篇博客里讲解了JAVA的线程的内存模型,见:JAVA并发编程2_线程安全&内存模型,接着上一篇提到的问题解决多线程共享资源的情况下的线程安全问题. 不安全线程分析 public clas ...

最新文章

  1. 业界首个!华为联合中国信通院等发布《网络体系强基展望白皮书》
  2. sql_1-2_get基于盲注
  3. 简明 XHTML 1.0 参考手册
  4. SpringMVC拦截器-interceptor和filter区别
  5. python qq模块_常用的Python模块
  6. TreeMap1.8源码
  7. 爱奇艺NLP:BiLSTM_CRF的关键词自动抽取
  8. ubuntu 开启dhcp服务并配置
  9. 一个事务复制的bug--更新丢失 续
  10. Hbase下载、安装流程
  11. 改变世界的程序员—Jack Dorsey
  12. 关于iOS 阴历阳历转化的那些事儿
  13. 【课程全解】-UML软件建模设计
  14. 新来的CTO规定所有接口都用 post 请求...
  15. VSCODE 全局搜索失效
  16. android手机的语音助手在哪里设置,华为语音助手在哪设置 华为语音助手唤醒具体步骤...
  17. 毕业设计 - 题目:基于机器视觉的图像矫正 (以车牌识别为例) - 图像畸变校正
  18. C++的STL中accumulate的用法
  19. ubuntu安装pandas
  20. 线性泛函分析之对偶基

热门文章

  1. c语言课程设计怎么做,C语言课程设计————写下流程图! 谢谢
  2. isp 图像算法(四)之white balance gain control 就是对 r,gr,gb,b 进行加权
  3. 用 pytorch 实现 一个rnn
  4. opencv 霍夫曼变换 直线提取
  5. 双代号网络图节点编号原则_『干货』二级建造师考试高频考点 双代号网络图的详细解析...
  6. 86. Leetcode 264. 丑数 II (动态规划-基础题)
  7. 无监督算法与异常检测
  8. Git 笔记:基本操作工作流程
  9. 说了这么久中台,那你知道中台是什么?在治什么病吗?
  10. UBUNTU衍生版制作,系统的封装