您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

自旋锁、排队自旋锁、MCS锁、CLH锁

时间:2019-10-14 11:56:55  来源:  作者:

自旋锁

自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。

简单的实现

import JAVA.util.concurrent.atomic.AtomicReference;

public class SpinLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

Thread currentThread = Thread.currentThread();

// 如果锁未被占用,则设置当前线程为锁的拥有者

while (!owner.compareAndSet(null, currentThread)) {

}

}

public void unlock() {

Thread currentThread = Thread.currentThread();

// 只有锁的拥有者才能释放锁

owner.compareAndSet(currentThread, null);

}

}

SimpleSpinLock里有一个owner属性持有锁当前拥有者的线程的引用,如果该引用为null,则表示锁未被占用,不为null则被占用。

这里用AtomicReference是为了使用它的原子性的compareAndSet方法(CAS操作),解决了多线程并发操作导致数据不一致的问题,确保其他线程可以看到锁的真实状态。

缺点

  1. CAS操作需要硬件的配合;
  2. 保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)的数据一致性,通讯开销很大,在多处理器系统上更严重;
  3. 没法保证公平性,不保证等待进程/线程按照FIFO顺序获得锁。

排队自旋锁(Ticket Lock)

Ticket Lock 是为了解决上面的公平性问题,类似于现实中银行柜台的排队叫号:锁拥有一个服务号,表示正在服务的线程,还有一个排队号;每个线程尝试获取锁之前先拿一个排队号,然后不断轮询锁的当前服务号是否是自己的排队号,如果是,则表示自己拥有了锁,不是则继续轮询。

当线程释放锁时,将服务号加1,这样下一个线程看到这个变化,就退出自旋。

简单的实现

import java.util.concurrent.atomic.AtomicInteger;

public class TicketLock {

private AtomicInteger serviceNum = new AtomicInteger(); // 服务号

private AtomicInteger ticketNum = new AtomicInteger(); // 排队号

public int lock() {

// 首先原子性地获得一个排队号

int myTicketNum = ticketNum.getAndIncrement();

// 只要当前服务号不是自己的就不断轮询

while (serviceNum.get() != myTicketNum) {

}

return myTicketNum;

}

public void unlock(int myTicket) {

// 只有当前线程拥有者才能释放锁

int next = myTicket + 1;

serviceNum.compareAndSet(myTicket, next);

}

}

缺点

Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。

下面介绍的CLH锁和MCS锁都是为了解决这个问题的。

MCS 来自于其发明人名字的首字母: John Mellor-Crummey和Michael Scott。

CLH的发明人是:Craig,Landin and Hagersten。

MCS锁

MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class MCSLock {

public static class MCSNode {

volatile MCSNode next;

volatile boolean isBlock = true; // 默认是在等待锁

}

volatile MCSNode queue;// 指向最后一个申请锁的MCSNode

private static final AtomicReferenceFieldUpdater UPDATER = AtomicReferenceFieldUpdater

.newUpdater(MCSLock.class, MCSNode.class, "queue");

public void lock(MCSNode currentThread) {

MCSNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1

if (predecessor != null) {

predecessor.next = currentThread;// step 2

while (currentThread.isBlock) {// step 3

}

}else { // 只有一个线程在使用锁,没有前驱来通知它,所以得自己标记自己为非阻塞

currentThread. isBlock = false;

}

}

public void unlock(MCSNode currentThread) {

if (currentThread.isBlock) {// 锁拥有者进行释放锁才有意义

return;

}

if (currentThread.next == null) {// 检查是否有人排在自己后面

if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4

// compareAndSet返回true表示确实没有人排在自己后面

return;

} else {

// 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者

// 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完

while (currentThread.next == null) { // step 5

}

}

}

currentThread.next.isBlock = false;

currentThread.next = null;// for GC

}

}

CLH锁

CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class CLHLock {

public static class CLHNode {

private volatile boolean isLocked = true; // 默认是在等待锁

}

@SuppressWarnings("unused" )

private volatile CLHNode tail ;

private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater

. newUpdater(CLHLock.class, CLHNode .class , "tail" );

public void lock(CLHNode currentThread) {

CLHNode preNode = UPDATER.getAndSet( this, currentThread);

if(preNode != null) {//已有线程占用了锁,进入自旋

while(preNode.isLocked ) {

}

}

}

public void unlock(CLHNode currentThread) {

// 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。

if (!UPDATER .compareAndSet(this, currentThread, null)) {

// 还有后续线程

currentThread. isLocked = false ;// 改变状态,让后续线程结束自旋

}

}

}

CLH锁 与 MCS锁 的比较

下图是CLH锁和MCS锁队列图示:

自旋锁、排队自旋锁、MCS锁、CLH锁

差异:

  1. 从代码实现来看,CLH比MCS要简单得多。
  2. 从自旋的条件来看,CLH是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋。
  3. 从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。
  4. CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。
  5. CLH锁可以更容易地去实现“取消 (cancellation)”和“超时”功能,

注意:这里实现的锁都是独占的,且不能重入的。



Tags:自旋锁   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
自旋锁:如果内核配置为SMP系统,自旋锁就按SMP系统上的要求来实现真正的自旋等待,但是对于UP系统,自旋锁仅做抢占和中断操作,没有实现真正的“自旋”。如果配置了CONFIG_DEBUG_SPI...【详细内容】
2021-10-21  Tags: 自旋锁  点击:(38)  评论:(0)  加入收藏
原始自旋锁最原始的自旋锁就是多个线程不断自旋,大家都不断尝试获取锁。看下面例子,主要看lock和unlock两个方法,Unsafe仅仅是为操作提供了硬件级别的原子CAS操作。对于lock方...【详细内容】
2020-08-20  Tags: 自旋锁  点击:(99)  评论:(0)  加入收藏
x一、互斥锁(同步)&emsp;&emsp;在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),...【详细内容】
2020-07-17  Tags: 自旋锁  点击:(73)  评论:(0)  加入收藏
自旋锁自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。自旋锁适用于锁保护的临界区很小的情况,临...【详细内容】
2019-10-14  Tags: 自旋锁  点击:(105)  评论:(0)  加入收藏
自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打...【详细内容】
2019-09-23  Tags: 自旋锁  点击:(149)  评论:(0)  加入收藏
▌简易百科推荐
为了构建高并发、高可用的系统架构,压测、容量预估必不可少,在发现系统瓶颈后,需要有针对性地扩容、优化。结合楼主的经验和知识,本文做一个简单的总结,欢迎探讨。1、QPS保障目标...【详细内容】
2021-12-27  大数据架构师    Tags:架构   点击:(5)  评论:(0)  加入收藏
前言 单片机开发中,我们往往首先接触裸机系统,然后到RTOS,那么它们的软件架构是什么?这是我们开发人员必须认真考虑的问题。在实际项目中,首先选择软件架构是非常重要的,接下来我...【详细内容】
2021-12-23  正点原子原子哥    Tags:架构   点击:(7)  评论:(0)  加入收藏
现有数据架构难以支撑现代化应用的实现。 随着云计算产业的快速崛起,带动着各行各业开始自己的基于云的业务创新和信息架构现代化,云计算的可靠性、灵活性、按需计费的高性价...【详细内容】
2021-12-22    CSDN  Tags:数据架构   点击:(10)  评论:(0)  加入收藏
▶ 企业级项目结构封装释义 如果你刚毕业,作为Java新手程序员进入一家企业,拿到代码之后,你有什么感觉呢?如果你没有听过多模块、分布式这类的概念,那么多半会傻眼。为什么一个项...【详细内容】
2021-12-20  蜗牛学苑    Tags:微服务   点击:(9)  评论:(0)  加入收藏
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  青锋爱编程    Tags:后台架构   点击:(21)  评论:(0)  加入收藏
在了解连接池之前,我们需要对长、短链接建立初步认识。我们都知道,网络通信大部分都是基于TCP/IP协议,数据传输之前,双方通过“三次握手”建立连接,当数据传输完成之后,又通过“四次挥手”释放连接,以下是“三次握手”与“四...【详细内容】
2021-12-14  架构即人生    Tags:连接池   点击:(17)  评论:(0)  加入收藏
随着移动互联网技术的快速发展,在新业务、新领域、新场景的驱动下,基于传统大型机的服务部署方式,不仅难以适应快速增长的业务需求,而且持续耗费高昂的成本,从而使得各大生产厂商...【详细内容】
2021-12-08  架构驿站    Tags:分布式系统   点击:(23)  评论:(0)  加入收藏
本系列为 Netty 学习笔记,本篇介绍总结Java NIO 网络编程。Netty 作为一个异步的、事件驱动的网络应用程序框架,也是基于NIO的客户、服务器端的编程框架。其对 Java NIO 底层...【详细内容】
2021-12-07  大数据架构师    Tags:Netty   点击:(17)  评论:(0)  加入收藏
前面谈过很多关于数字化转型,云原生,微服务方面的文章。虽然自己一直做大集团的SOA集成平台咨询规划和建设项目,但是当前传统企业数字化转型,国产化和自主可控,云原生,微服务是不...【详细内容】
2021-12-06  人月聊IT    Tags:架构   点击:(23)  评论:(0)  加入收藏
微服务看似是完美的解决方案。从理论上来说,微服务提高了开发速度,而且还可以单独扩展应用的某个部分。但实际上,微服务带有一定的隐形成本。我认为,没有亲自动手构建微服务的经历,就无法真正了解其复杂性。...【详细内容】
2021-11-26  GreekDataGuy  CSDN  Tags:单体应用   点击:(35)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条