专栏首页洁癖是一只狗并发编程原子性问题

并发编程原子性问题

原子性问题到底如何解决呢

原子性的问题是因为线程切换,如果能够禁用线程那不就可以解决问题了吗,而操作系统做线程切换是依赖CPU中断,所以禁止CPU发生中断就可以禁止线程切换

在早期单核CPU时代,这个方案是可行的,但是在多核CPU场景就是不适应的,比如在32CPU上执行long变量的写操作说明这个问题,long类型变量是64位,在32位CPU上会被拆成两次写操作如下图

在单核CPU,同一时刻只有一个线程执行,当我们禁止CPU中断,就可以避免线程切换,这是CPU使用权的线程就可以不断的执行,做一两次写操作就一定要么执行成功,要么执行失败,保证了原子性

在多核CPU上,并不能保证同一时刻只有一个线程,比如有两个线程分别在不同的CPU上执行,禁止CPU中断,只能保证CPU上的线程连续执行,但是如何此时两个线程同时操作高32的值,就会出现bug.

同一时刻只有一个线程,称之为互斥,只要保证了对共享变量的互斥,不管在单核还是在多核CPU上都能保证原子性

简单锁模型

一般我理解的锁的样子如下图

我们把互斥执行的代码成为临时区,线程在进入临时去之前,首先尝试加锁,如果成功,则进入临时去,此时我们就有对这个线程持有锁,否则等待,知道持有锁的线程释放锁,持有锁的线程执行完临界区的代码后,执行解锁unlock看起很完美,但是我们忽略了两点

  1. 我们锁的是什么
  2. 我们保护的又是什么

改进后的锁模型

在现实生活中,你用你家的锁,锁住你家的门,我用我家的锁,锁住我家的门,在并发编程世界里,也是一样的,这个关系正如上图一样

首先,我们要把受保护资源R标注出来,如图上的受保护资源R,其次我们要保护资源R就得为它创建一把锁LR,最后针对这个锁LR,我们还需在进出临界区添加锁,和解锁操作,同时在锁LR和受保护的资源R之间有一条关联,正如上面的那条线,如果我们用自家的锁,去锁别家的资源,就可能导致bug出现。

锁技术:synchronized

synchronized关键字就是一种锁的实现,他可以修饰方法,也可以修饰代码块,如下图

class X {
// 修饰非静态方法
synchronized void foo() {
// 临界区
  }
// 修饰静态方法
synchronized static void bar() {
// 临界区
  }
// 修饰代码块
  Object obj = new Object();
void baz() {
synchronized(obj) {
// 临界区
    }
  }
}

我们发现好像没有加锁和解锁的操作,其实synchronized内部实现了加解锁的操作,这样锁是为了避免我们遗忘加锁操作,否则会出现致命的bug

同时上面加锁的对象有两条默认规则如下

  • 当修饰静态方法的时候,锁定的是当前类的class对象
  • 当修饰非静态方法的时候,锁定的就是当前对象this

上面代码可以改成下面

class X {
// 修饰静态方法
synchronized(X.class) static void bar() {
// 临界区
  }
}
class X {
// 修饰非静态方法
synchronized(this) void foo() {
// 临界区
  }
}

使用synchronized解决问题

如下面代码我们有类safecalc,一个属性value,两个方法get和addOne

class SafeCalc {
long value = 0L;
long get() {
return value;
  }
synchronized void addOne() {
value += 1;
  }
}

其中addOne是被syncronized修饰,而get没有修饰,都是对value变量的有操作,那么有没有问题呢

先拿addOne方法解释,首先使用了syncronized修饰后,就可以保证无论在单核还是多核,都可以保证原子操作,且保证了线程的可见性

管程中锁的规则:对一个锁的解锁 Happens-Before 于后续对这个锁的加锁

按照上面规则如果多个线程使用addOne方法,可见性可以保证,也就说有1000个线程执行addOne方法,最终的结果就是1000,

看上去还是很完美,但是我们忘记了get方法,因为管程中锁的规则是只能保证后续操作对这个锁的加锁的可见性,而get并没有加锁操作,因此并不能保证可见性,这个问题也很简单,只要加上synchronized,就可以解决

class SafeCalc {
long value = 0L;
synchronized long get() {
return value;
  }
synchronized void addOne() {
value += 1;
  }
}

上面代码get和addOne方法都需要访问value这个受保护的资源,这个资源使用this对象这把锁保护,线程进入临界区get和addone,必须获取this这把锁,因此get和addone互斥,这样就可以避免并发问题

这里就像球场的门票管理一样,一个座位只能有一个人使用,这个座位就是受保护的资源,而入场就是Java类中的方法,而门票就是保护资源的锁,java检票就由synchronized执行

锁和受保护资源的关系

受保护资源和锁的关系是N:1的关系一把锁可以锁多个资源,对应的现实中就是球赛的作为让你包场了

我们把上面的例子修改一下,看看有没有并发问题

class SafeCalc {
static long value = 0L;
synchronized long get() {
return value;
  }
synchronized static void addOne() {
value += 1;
  }
}

我们发现get和addone分别用两把不同的锁,get使用的this,而addOne使用的safecalc.Class,此时由于是不同的两把锁,而临界区没有互斥关系,因此两个方法对临界区的value就没有办法保证可见性,就会引发并发问题,如下图

本文分享自微信公众号 - 洁癖是一只狗(rookie-dog),作者:洁癖汪

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-10-26

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 为什么会有Lock

    在并发编程的领域中,有两个核心问题,一个是互斥,即同一时刻只有一个线程访问共享资,一个是同步,即线程之间如何通讯,协作,这两大问题,管程都能够实现,在java ...

    小土豆Yuki
  • Semaphore,ReadWriteLock,StampedLock

    上面三个方法都是原子性的,并且这个原子性是由信号量模型实现放保证的,在java中信号量的实现是有类Semaphore实现的,下面看看下面代码,

    小土豆Yuki
  • 并发编程常识

    建议使用notifyAll,两个虽然都是唤醒线程,但是还是有区别的,notify是随机唤醒等待线程池的一个线程,而notifyAll会唤醒对象等待队列池的所有线...

    小土豆Yuki
  • postMessage 消息传递

            web开发了,除了前台与服务器交换数据,还有可能前台页面间需要进行数据传递,比如窗口间,页面和嵌套的iframe间。这些问题之前都有解决办法,但...

    用户2038589
  • 梯度下降(Gradient Descent)小结

        在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的方法是最小二乘法。这里就...

    刘建平Pinard
  • 那个男人再发力,原来我以前学的 Lambda 都是假的

    大家好,我是扔物线朱凯。Kotlin 很方便,但有时候也让人头疼,而且越方便的地方越让人头疼,比如 Lambda 表达式。很多人因为 Lambda 而被 Kot...

    用户1907613
  • 面试问烂的 Spring AOP 原理、SpringMVC 过程

    Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以。但今天笔者带大家一起深入浅出源码,看看他的原理。以期让...

    JAVA葵花宝典
  • 【码上开学】Kotlin 的高阶函数、匿名函数和 Lambda 表达式

    大家好,我是扔物线朱凯。Kotlin 很方便,但有时候也让人头疼,而且越方便的地方越让人头疼,比如 Lambda 表达式。很多人因为 Lambda 而被 Kot...

    扔物线
  • 邮箱安全服务专题 | 发现邮箱风险,在发生安全事件之前

    邮件服务占据互联网应用的“半壁江山",境外攻击者通过大范围针对邮箱系统扫描攻击,来窃取资料,从邮件系统诞生针对邮件系统的安全攻击从来没有间断过。并伴随着攻击手法...

    安恒信息
  • 领域对象

    领域对象(domain object)换种说法叫做实体类,大家应该就比较熟悉了。在一个具体的项目中,我们通常需要把业务中需要用到的数据抽象出来组成一个实体类,通...

    赵哥窟

扫码关注云+社区

领取腾讯云代金券