volatile和synchronized 实现原理的差别

提到volatile 和 synchronized 的时候不得不提到的一个东西就是JMM(java Memory Model)java内存模型。

因为在并发的过程中 经常要处理一些 可见性 、 原子性 、 有序性的问题。

并发编程中的两个关键问题是: 线程之间是 如何通信的。

这又分两种情况: 1、共享内存 ——— 隐式通信 volatile 2、 消息传递 ——— 显示通信 synchronized / lock

先看看没有 volatile 跟 synchronized来控制 线程的通信会出现什么问题

线程A把X = 0 改成 X = 1 时, 线程2 并不会马上知道。

什么时候要让线程2去解决这个问题,就成了解决线程直接通信的一个关键。

这也就是 可见性问题的所在。

java 提供了 volatile 和 synchronized 关键字 来处理这个可见性的问题,当然 使用lock 也可以,但这里先暂不做讨论。

先说synchronized是怎么做到的,synchronized 先锁住 共享的内存变量,然后线程A修改完之后将值返回到主内存,然后线程B获得锁以后才能获取值,所以通过代码层面的锁可以解决这个线程之间通信的 可见性问题。

但是代码层面,使用锁的话性能就低了,更好的解决办法是 使用volatile 关键字 来修饰这个共享变量。

下面我们来深入的分析 一下volatile 为什么就能做到比synchronized 性能好 还能保持可见性:

1、对于声明了 volatile 的变量,进行写操作的时候, JVM 会向 处理器发送一条 lock 的前缀指令。 2、将这个变量在自己工作内存的值,强制写回主内存。

此时就算线程A将 X 变量的最新值 写回了主存, 但是线程B不去拿,那线程B自己工作内存里的值也还是旧的,那主内存准备通知线程B 去刷新它自己工作内存中的值呢,所以接着看第三步。

3、在多处理器的情况下,保证各个处理器缓存一致性的特点,就会实现缓存一致性协议。 意思就是:每个处理器会 嗅探到 总线上的所传播的数据来检测自己缓存中的值是不是过期了, 当处理器的缓存对应的内存地址被修改以后,它就会将当前的处理器缓存的值设置为失效状态,然后去读那个最新的值。

这就是volatile 的实现原理,代码层面看起来没有加锁,实际上底层还是加了锁的。但是被JVM优化得很快了,它实现锁的开销很小。

再来看看synchronized的实现原理。 先看一段代码:

public class App {

    public static void main(String[] args) {
        test();
    }

    public static synchronized void test(){
    } 
}

将这段代码编译后,打开命令行,进入App.class所在目录 执行 javap -v App.class命令

可以看到App.class的字节码:

我们把目光放到 第 4 行跟 第 6 行。 这就是synchronized的作用,调用了

monitorenter 跟 monitorexit 的指令, 这是基于JVM 实现的。

我们通过synchronized 声明了锁的范围, 当前的App对象会有一个自己的监视器,该监视器必须获得 当前对象的锁之后 monitorenter 才有资格去 调用 当前的这个线程方法,也就是字节码的第四行。然后锁运行完以后 就运行第六行指令 将锁释放。

ok 上个图估计会更好容易理解一点:

多个请求进来,想要获取同步代码块的锁,必须先获取 monitorenter ,但monitorenter 只有一个,所以 其它线程 B、C、D、E 就都获取不到锁了,没有获取到锁的线程会被放到一个 synchronizedQueue的队列里。

然后获取到monitorenter 的那个线程 在执行完同步代码后,会 monitor.exit 。通知队列里的 B C D E 线程再去竞争锁。

这就是synchronized 的原理啦。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python绿色通道

Python分布式进程

分布式进程是指的是将Process进程分布到多台机器上,充分利用多台机器的性能完成复杂的任务。在Thread和Process中,应当优选Process,因为Pr...

19210
来自专栏Petrichor的专栏

pycharm: 跳转 & 回退

26820
来自专栏我的博客

CI页面乱码

今天项目页面乱码 我确认了数据库读取时utf8,页面也添加 <meta http-equiv=”content-type” content=”text/html...

30960
来自专栏专注数据中心高性能网络技术研发

Find命令-Linux系统搜索利器

1.Motivation ---- 搜索查找是管理文件系统常用的操作,虽然动作逻辑本质上是匹配,很简单,但搜索也有很多种花样,可以用来加速搜索,快速提取想要的内...

31550
来自专栏河湾欢儿的专栏

常用的dos命令

10320
来自专栏康怀帅的专栏

GitHub Pages 常见问题

本文列举了一些使用 GitHub Pages 遇到的问题及其解决方法。 资源 404 你可以使用以下方法中的一种来解决该问题。 禁用 jekyll 以 _下划线...

31430
来自专栏Pythonista

python模块和包

一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py 的后缀。

22210

如何用split命令来拆分文件

split是一个类似于grep或tail的Unix命令行实用程序。它允许您将较大的文件分成几个较小的文件。

13940
来自专栏我是攻城师

使用shell分页读取600万+的MySQL数据脚本

42750
来自专栏技术博文

在vim中快速复制粘贴多行

用vim写代码时,经常遇到这样的场景,复制多行,然后粘贴。 我现在这样做: 1. 将光标移动到要复制的文本开始的地方,按V(是大写V)进入可视模式。 2. 将光...

637100

扫码关注云+社区

领取腾讯云代金券