竹笋

首页 » 问答 » 环境 » mutex,semaphore和barr
TUhjnbcbe - 2024/3/1 16:39:00

锁的实现。mutex,semaphore和barrier是什么?重量级锁,轻量级锁和偏向锁的区别?

开始

今天,我们继续来聊java中的各种锁。

锁的实现

我们先来聊聊锁的实现。当然,不会聊的很具体,但是,通过在思维链路中构建这样一个框架,可以帮助更好的理解Java中的各种锁。

要实现锁,我们大致要解决以下4个问题:

1.谁拿了?换句话说,我们需要以某种方式来标记,当前是哪个线程拿到了某个资源的锁。

2.怎么拿?有多个线程在抢同一把锁,他们之间怎么竞争?应该让谁拿,不让谁拿?这是我们在构建锁时应该考虑的问题。

3.拿不到怎么办?既然是抢,那怎么有抢不到的时候。抢不到怎么办?继续等着还是罢工?这也需要我们去设计。

4.用完了怎么办?我们是有文化有修养的文明人。用完了是不是应该告诉别人一声呢?

想象一下排队等公厕的场景。基于文明建设的需要,这个例子就省略了。JVM为了实现锁,实现了一些相关的底层机制,如mutex,semaphore,barrier等。

semaphore

要想深入的理解这些底层机制,是一件很困难且复杂的事情,也不是本文的重点,所以,我们只是简单描述一下它们的基本原理和使用场景。

semaphore,信号量,它可能会有多个属性值,代表某个资源或资源集合,能接受线程的数量。什么意思?可以用停车场作类比。假设一个停车场,目前有10个空位,那么,其信号量就是10,代表门口的保安可以放10辆车进去。每当有车辆进或出时,信号量都会相应的增或减。

mutex

mutex相比semaphore来说,是一种更细化的机制。代表某个资源是否正在被某个线程所使用,1代表是,0代表否。线程在获取某资源的锁时,将mutex置为1,释放锁时,将mutex置为0。

还是以停车场为例,如果semaphore是整个停车场的信号量,那么mutex就是单个车位的信号量,对个某个车位来说,只有空或非空两种状态,所以mutex又叫互斥量。

barrier

程序在执行时,访问内存的顺序和代码中编写的访问顺序是不一致的。这个问题的产生一般有两个原因,一是程序为了优化而进行的指令重排,另一个是多CPU交互引起的内存乱序访问。

所以,有时候,我们为了保护我们设定的内存访问顺序,就有可能需要用到barrier。barrier,直译为屏障,事实上是一种内存屏障。它能保证,屏障后的指令不会因为各种原因被提到屏障前的。换句话,barrier保证了屏障前的指令的执行顺序,一定在屏障后的指令之前。

markword

JVM中,Java对象的数据结构中会包含一个称之为markword的字段,在32位和64位的机器中分别占用32bit和64bit(未开启压缩指针)。这个字段用于存储当前对象的状态。

注意这个表格中跟锁相关的内容,我们发现有三种,分别是轻量级锁,重量级锁和偏向锁。

自旋锁

自旋锁我们之前聊过,它其实本质上来说,并不是一个锁,只是线程通过执行空代码的方式在等待。这种方式只适用于乐观锁的情况,在悲观锁的情况下,资源竞争激烈,可能存在很多线程都在不断的等待。此时,自旋的消耗会比线程挂起的消耗还要大。就不适合使用自旋了。

重量级锁

重量级锁的底层实现机制就是mutex,即使用了资源对象上的互斥量。synchronized就是典型的重量级锁的实现。这种锁的机制,可以最大力量的保证同步,但是,也需要消耗最多的资源。

轻量级锁

重量级锁的开销是很大的。但是,实际在程序的运行中,很多场景下,绝大多数时候没有实际的锁竞争,此时,如果去使用重量级锁,是没有意义的,还是造成资源的浪费。所以,轻量级的做法是,将MarkWord中的部分字节CAS更新指向线程栈中的LockRecord,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。

偏向锁

在没有实际竞争的情况下,事实上,还可以针对轻量级锁继续优化。因为在实际使用时,发现对某个资源加锁的,通常都是同一个线程。于是,就有了偏向锁。

“偏向”的意思是,偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此,只需要在MarkWord中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁。

1
查看完整版本: mutex,semaphore和barr