synchronized 关键字


$ java -version
java version “13.0.2” 2020-01-14
Java(TM) SE Runtime Environment (build 13.0.2+8)
Java HotSpot(TM) 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)

Java 中的 synchronized 关键字浅析

题外话

我们常说, 要避免多线程的上下文切换。

这里我想问两个问题:

  • 什么是上下文切换?
  • 为什么要避免上下文切换?

在回答上面的问题之前, 我们先大致了解下 CPU 的内部结构(只列出关键部分, 图示为一个包含两个核心的 CPU)

image-20210417191455636

如图所示, 一个 CPU 包含多个核心, 每个核心除了包含逻辑计算单元(ALU)、寄存器、程序计数器等外, 还会囊括 L1、L2 两级缓存。

L3 缓存为 CPU 级别的缓存, 主存则是所有 CPU 共享。

CPU 执行速度非常快,快到主存的读写速度跟不上,于是出现了 L1、L2、L3 三级缓存的设置。至于为什么是 3 级缓存,以及各级缓存是不是越大越好,作者作为一只菜鸡,就不得而知了。


好了, 在了解了 CPU 的基本架构后, 我们来说说第一个问题:

  • 什么是上下文切换?

在我之前的文章有提到:

CPU 通过时间片分配算法来循环执行任务, 一个时间片一般为几十毫秒,而时间片是用来给各个线程运行指令的时间。

当线程 A 消耗完时间片后, CPU 需要保存当前线程的状态,这样,当时间片再次分配给线程 A 时,可以知道它之前的执行状态。

这里,从保存到在加载的过程,就是一次上下文切换。

参考上图, 有一个线程 t1 在 core1 执行, 时间片结束后, 轮到 t2 执行,在 t2 执行前, 需要先把 t1 的上下文保存起来,等到 t1 再次轮到时,先把之前保存起来的上下文恢复,再开始执行 t1。 这样的一个过程,就是上下文切换。

知道上下文切换的含义后, 第二个问题自然而然就明白了,避免上下文切换,就可以避免保存和重新加载上下文,避免对 CPU 的浪费。

用户态和内核态

计算机硬件资源是有限的,如果所有的应用程序都能任意申请和使用有限的硬件资源,那 server 很快就会被玩坏。

于是 Linux 操作系统针对不同的操作,赋予不同的权限,而不同的两级权限,对应的就是我们的用户态内核态

用户态如果想申请硬件资源如内存分配,需要通过系统调用,由内核态来分配。

完成一次系统调用,需要先将当前的用户态信息保存下来, 然后切换到内核态执行,得到结果后,再切回用户态并恢复现场


轻量级锁和重量级锁

我们说这把锁是轻量级锁,意思是这把锁在用户态就能完成,不会进行系统调用,即不会有用户态、内核态的切换。 重量级锁则会发生系统调用。

java 中的轻量级锁,即 CAS,compareAndSwap,即 自旋锁。 CAS 是在用户态调用。

我们查看 java 的代码,可以看到这样一个本地方法:

    // jdk.internal.misc.Unsafe#getAndAddInt  -- 1
    @HotSpotIntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }

    // jdk.internal.misc.Unsafe#compareAndSetInt  -- 2
    @HotSpotIntrinsicCandidate
    public final native boolean compareAndSetInt(Object o, long offset,
                                                 int expected,
                                                 int x);

2 处是一个 native 方法,我们需要知道的是,该方法能向我们保证是一个原子操作。(通过 hotspot 的源码可以知道, 是通过 lock cmpxchg 指令实现原子操作的)。

1 处则是通过 do... while... 自旋的方式,不断尝试设置新的值,直到成功。

所以我们说, CAS 是自旋锁


对于重量级锁, 用户态程序需要首先向操作系统申请锁, 简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资。


java 对象在内存中的布局

JOL

synchronized 工作原理

参考资料


文章作者: hexiao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 hexiao !
 上一篇
vim plug vim plug
vim plug scripts
2021-08-12
下一篇 
长度最小的子数组 长度最小的子数组
题目 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。 示例: 输入:s = 7, nums = [2,3,1,2,4,3]输
2020-10-20
  目录