专栏首页武培轩的专栏一男子给对象转账5000元,居然又退还了!

一男子给对象转账5000元,居然又退还了!

在并发编程中,所有问题的根源就是可见性、原子性和有序性问题,这篇文章我们就来聊聊原子性问题。

在介绍原子性问题之前,先来说下线程安全:

线程安全

我理解的线程安全就是不管单线程还是多线程并发的时候,始终能保证运行的正确性,那么这个类就是线程安全的。

其中在《Java并发编程实战》一书中对线程安全的定义如下:

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

为了保证线程安全,可能会有很多的挑战和问题,当我们了解了问题根源所在,问题也就迎刃而解了,接下来介绍线程安全三大特性之一的原子性。

原子性

原子,我想大家应该都有印象吧,在化学反应中不可再分的基本微粒就是原子,也就是不可分割。

同时事务的四大特性 ACID 中也有原子性,那么原子性究竟是什么呢?

原子性其实就是所有操作要么全部成功,要么全部失败,这些操作是不可拆分的,也可以简单地理解为不可分割性。

将整个操作视作一个整体是原子性的核心特征,这些操作就是原子性操作

接下来举个原子性操作在生活中的例子:

比如,wupx 今天刚发了 5100 元的工资,全身家当为 5100 元,huxy 目前余额还有 1000 元,此时 wupx 上交 5000 元,如果转账成功,则 huxy 的余额就变为了 6000 元,wupx 的余额为 100 元。

若转账失败,则转出去的余额会退回来,wupx 的余额仍然是 5100 元,huxy 的余额为 1000 元。

不会出现 wupx 的钱转出去了,huxy 的余额没有增加,或者 wupx 的工资没转出去,而 huxy 的余额却增加的情况。

wupx 上交工资给 huxy 的操作就是原子性操作,wupx 余额减少 5000 元,而 huxy 的余额增加 5000 元的操作是不可分割和拆分的,正如我们上面说到的:要么全部成功,要么全部失败。wupxhuxy 上交成功流程如下所示:

原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。

到这里,我相信大家对原子性有了基本的了解,下面来聊下原子性问题。

原子性问题

原子性问题的核心就是线程切换导致的,因为并发编程中,线程数设置的数目一般会大于 CPU 核心数。

关于线程数的设置可以阅读:线程数,射多少更舒适?

每个 CPU 同一时刻只能被一个线程使用,而 CPU 资源分配采用的是时间片轮转策略,也就是给每个线程分配一个时间片,线程在这个时间片内占用 CPU 的资源来执行任务,当过了一个时间片后,操作系统会重新选择一个线程来执行任务,这个过程一般称为任务切换,也叫做线程切换或者线程上下文切换。

上图就是线程切换的例子,有 Thread-0Thread-1 两个线程,其中粉色矩形表示该线程占有 CPU 资源并执行任务,刚开始 Thread-1 执行一段时间,这段时间称为时间片,在该时间片内,Thread-1 会占有 CPU 资源并执行任务,当经过一个时间片后,Thread-1 会让出 CPU 资源,虚线部分表示让出 CPU,不占用 CPU 资源,CPU 会重新选择一个线程 Thread-0 来执行,CPU 会在 Thread-0Thread-1 之间来回切换,反复横跳。

下面通过一个例子来看下原子性问题,具体代码如下:

public class AtomicityDemo {

    private long count = 0;

    public void calc() {
        count++;
    }
}

calc() 方法中只有一个 count++ 操作,那么就是原子性的吗?

下面在 class 目录下使用 javap -c AtomicityDemo 就可以得到如下结果:

public class com.`wupx`.thread.AtomicityDemo {
  public com.`wupx`.thread.AtomicityDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: lconst_0
       6: putfield      #2                  // Field count:J
       9: return

  public void calc();
    Code:
       0: aload_0
       1: dup
       2: getfield      #2                  // Field count:J
       5: lconst_1
       6: ladd
       7: putfield      #2                  // Field count:J
      10: return
}

重点来看下 calc 方法,这些 CPU 指令大概可以分为如下三步:

  • 指令 1:将 count 从内存加载到 CPU 寄存器
  • 指令 2:在寄存器中执行 +1 操作
  • 指令 3:将结果写入内存(也有可能是 CPU 缓存)

关于 CPU 缓存可以阅读:原来 CPU 为程序性能优化做了这么多

操作系统的线程切换并不是一定是发生一条语句执行完成后,而可能是发生在任何一条 CPU 执行完成后。比如 Thread-0 执行完指令 1 后,操作系统发生了线程切换,两个线程都执行了 count++ 操作,但是最后的结果是 1 而不是 2,下面用图来表示这个过程。

通过上图,我们可以发现:Thread-1count=0 加载到 CPU 的寄存器后,发生了线程切换,此时内存中的 count 值为 0,Thread-0count=0 加载到 CPU 寄存器,执行 count++ 操作,并将 count=1 写到内存,此时,CPU 切换到 Thread-1,执行 Thread-1 中的 count++ 操作后,Thread-1 中的 count 值为 1,Thread-1count=1 写入内存,此时内存中的 count 值为 1。

因此,在并发编程中,若在 CPU 中存在正在执行的线程,正好 CPU 发生了线程切换,则可能会导致原子性问题,这就是导致并发编程问题的根源之一。

针对原子性问题,我们可以通过为操作加锁或者使用原子变量来解决,原子变量在 java.util.concurrent.atomic 包中,是 JDK 1.5 引入的,它提供了一系列的原子操作。

总结

这篇文章简要介绍了线程安全的概念,并详细介绍了线程安全的特性之一原子性,并针对原子性问题进行了分析。

只有掌握了引发原子性问题的根源,才能便于我们编写更加安全的并发程序。

欢迎大家留言讨论,分享你的想法。

参考 《Java并发编程实战》 Java并发编程实战

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 我就站在你面前,你却视而不见!

    在上一篇文章一男子给对象转账5000元,居然又退还了!中,我们学习了并发三大特性之一的原子性,并对原子性问题进行分析。

    武培轩
  • eos原力:15天目睹万币侯诞生 ·商战备忘录

    我运营了一个超级节点,是EOS原力主链启动的23个超级节点之一,按照游戏的规则,保持名次在23名以内,就可以每24小时获得11000枚原力EOS币。

    区块链领域
  • 2017年网络诈骗趋势研究报告

    摘要 2017年猎网平台共收到全国用户提交的有效网络诈骗举报24260例,举报总金额3.50亿余元,人均损失14413.4元。与2016年相比,网络诈骗的举报数...

    FB客服
  • Python MySQL数据库交互

    可能默认的源安装第三库会有点慢,可以配置一下其他的镜像源。Pip安装第三方库网速慢(解决方案)

    忆想不到的晖
  • Python MySQL数据库交互

    可能默认的源安装第三库会有点慢,可以配置一下其他的镜像源。Pip安装第三方库网速慢(解决方案)[1]

    ZackSock
  • 转发承包你一年零花钱、转账1000返还10000?

    前两天,还在读书的表弟突然炫耀,说自己被大V抽中一等奖,只要支付一点手续费,就能拿到一年的零花钱返现,真是一夜暴富! 一哥一听,一个马步向前就阻止了他:这是一...

    腾讯举报中心
  • 七夕消费哪家强?大数据揭秘单身狗才是最大赢家

    今年七夕节前人均礼品消费高达788元,60后老夫老妻人均花费852元为浪漫买单,天蝎座七夕消费居12星座首位……互联网金融平台挖财18日发布了一组七夕情人节消...

    灯塔大数据
  • 一周播报|迁移至集体户口很简单,但其中的五个坑需要你一辈子来填……

    上周有养码人开通了亚马逊AWS服务,原本一年内免费试用,但出现了扣款现象,这笔费用如何追回?

    养码场
  • 打打游戏就能在北京二环买套房?区块链大神说:你能靠VR刷怪升级还房贷!

    不得不感叹,太空真美,人类真渺小。刘慈欣将极致空灵的想象力与厚重的现实完美结合,在成功收获超高票房的同时,也预示了中国科幻电影的一次大跨步前行。

    区块链大本营
  • 李子柒经济学:全网8000万粉丝,顶流的变现之路

    这一个月以来,央视点赞、上杂志封面、接受新华社专访,这个1990年出生的四川女生彻底火了。

    iCDO互联网数据官
  • 银行内鬼程序员曝光:服务器植病毒,ATM取款717万,账户余额还不变

    现在,有个程序员实现了。通过往总行服务器植入病毒,每天ATM取款5千至2万,一年多内取走700多万,而且银行卡余额始终没有变化。

    量子位
  • V神要退位?这10件假期发生的重大新闻,你错过了吗?

    以太坊创始人Vitalik Buterin在推特上表示,就算没有我,以太坊网络也“绝对会存活下去”。在被问及将来是否会从以太坊上退居二线时,V神表示,“已经在进...

    区块链大本营
  • 【戏说】从风险投资角度出发,选老公究竟要用哪种估值法?

    作为私募投资机构人员,辉哥为了加深普通人士对私募股权投资估值及投资流程的理解,引用网络文章和配图整理了以下内容。本文仅作为娱乐、学习之用,如有冒犯,请联系删除。

    辉哥
  • 看看这份账单,月薪一万在北京只能过这样的日子...

    合租价三环平均一人都是3000+,四环也要2500+,在东西城和朝阳海淀等主要工作和教育地段还会更高,具体合租价格可以参考下图。

    51社保
  • php进阶

    基本数据类型和数组都为真复制,即为真副本,当属性为对象时,为假复制,改变副本仍会影响原对象.解决方案:

    后端技术探索
  • 帝都十年涨租史:迷失北京的“85后”

    2018年是北京奥运的第十个年头。十年前的6月中旬,在奥运会倒计时50天纪念日时,100名在北京学习的汶川地震绵竹灾区学生手拿奥运“福娃”,成为奥运支线,也就是...

    红狐财经
  • 年薪50万的人的生活方式

    税后五十来万,另有一年十万左右的利息收入和三万多房租。我们住内环两房,限购也无法再买房。

    用户3477080
  • 假装被骗,将计就计黑进印度骗子电脑揭开对方家底

    那一期中,Browning黑进了印度一个诈骗中心的电脑,全程揭露了诈骗犯行骗的过程,并在搜集证据之后报警....

    小林C语言
  • #互联网资讯早知道#

    1、双11每10个分期用户就有6个95后 2、工信部:6G概念研究今年已启动,下载速度每秒1TB 3、支付宝双11当天通过指纹和刷脸完成的支付逾60% 4、支付...

    程序员的酒和故事

扫码关注云+社区

领取腾讯云代金券