自旋锁(Spin lock)
自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。
简单的实现
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操作),解决了多线程并发操作导致数据不一致的问题,确保其他线程可以看到锁的真实状态。
缺点
- CAS操作需要硬件的配合;
- 保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)的数据一致性,通讯开销很大,在多处理器系统上更严重;
- 没法保证公平性,不保证等待进程/线程按照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。
CLH锁
CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class CLHLock {
public static class CLHNode {
private 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 currentThreadCLHNode) {
CLHNode preNode = UPDATER.getAndSet( this, currentThreadCLHNode); // 转载人注释: 把this里的"tail" 值设置成currentThreadCLHNode
if(preNode != null) {//已有线程占用了锁,进入自旋
while(preNode.isLocked ) {
}
}
}
public void unlock(CLHNode currentThreadCLHNode) {
// 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。
if (!UPDATER .compareAndSet(this, currentThreadCLHNode, null)) {
// 还有后续线程
currentThreadCLHNode. isLocked = false ;// 改变状态,让后续线程结束自旋
}
}
}
MCS锁
MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class MCSLock {
public static class MCSNode {
MCSNode next;
boolean isLocked = true; // 默认是在等待锁
}
volatile MCSNode queue ;// 指向最后一个申请锁的MCSNode
private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater
. newUpdater(MCSLock.class, MCSNode. class, "queue" );
public void lock(MCSNode currentThreadMcsNode) {
MCSNode predecessor = UPDATER.getAndSet(this, currentThreadMcsNode);// step 1
if (predecessor != null) {
predecessor.next = currentThreadMcsNode;// step 2
while (currentThreadMcsNode.isLocked ) {// step 3
}
}
}
public void unlock(MCSNode currentThreadMcsNode) {
if ( UPDATER.get( this ) == currentThreadMcsNode) {// 锁拥有者进行释放锁才有意义
if (currentThread.next == null) {// 检查是否有人排在自己后面
if (UPDATER.compareAndSet(this, currentThreadMcsNode, null)) {// step 4
// compareAndSet返回true表示确实没有人排在自己后面
return;
} else {
// 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
// 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完
while (currentThreadMcsNode.next == null) { // step 5
}
}
}
currentThreadMcsNode.next.isLocked = false;
currentThreadMcsNode.next = null;// for GC
}
}
}
下图是CLH锁和MCS锁队列图示:
差异:
- 从代码实现来看,CLH比MCS要简单得多。
- 从自旋的条件来看,CLH是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋
- 从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。
- CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。
注意:这里实现的锁都是独占的,且不能重入的。
http://blog.csdn.net/fei33423/article/details/30316377
相关推荐
知攻善防-应急响应靶机-web2.z18
知攻善防-应急响应靶机-web2.z09
白色简洁风格的影视众筹平台整站网站源码下载.zip
内容概要:本文详细解析了HTTP请求的整个流程,包括用户请求发起、请求报文构建、服务器处理请求、响应报文生成、网络传输响应和浏览器接收响应六个阶段。每个阶段的内容均涵盖了关键步骤和技术细节,如DNS解析、TCP连接、缓存策略、HTTP/2性能提升、HTTPS加密等。通过这些内容,读者可以全面理解HTTP请求的完整流程。 适合人群:具备一定网络基础知识的前端、后端开发人员及IT运维人员。 使用场景及目标:适用于希望深入了解HTTP协议及其优化技术的技术人员,有助于提升系统的性能和安全性,优化用户体验。 阅读建议:本文内容详尽且涉及多个关键技术点,建议读者结合实际案例进行学习,逐步理解和掌握各个阶段的技术细节和优化方法。
白色简洁风格的电话通讯公司模板下载.zip
白色简洁风格的日历当日事件提醒整站网站源码下载.zip
一键制作 歌曲伴奏! 可以消人声 吉他 鼓 等 多轨道声音。相当好用。
知攻善防-应急响应靶机-web2.z04
NSDocumentError如何解决.md
白色宽屏风格的大气冲浪运动整站网站模板.rar
白色简洁风格的婴儿用品商城网站模板.zip
罗兰贝格2023未来营养趋势报告21页
预览地址:https://blog.csdn.net/qq_42431718/article/details/144749829 html+css 圣诞树代码html
1-100加减乘除出题生成器
白色简洁风格的网络实验室CSS模板.zip
白色简洁风格的企业产品展示整站网站源码下载.zip
内容概要:《etcd-metrics-latest.txt》文档记录了 etcd(一个分布式键值存储系统)的多个指标数据,包括但不限于集群版本、认证修订版、后端磁盘操作延时分布、租赁管理、键值操作统计、快照保存、网络通信、Go 运行时指标、gRPC 请求处理、操作系统资源使用以及进程资源使用等。这些指标提供了详细的性能监测数据,帮助运维人员和开发人员理解和优化 etcd 集群的运行状态。 适合人群:具有基础计算机科学知识的运维人员或开发人员,尤其是负责维护或开发基于 etcd 技术系统的专业人员。 使用场景及目标:主要用于监控 etcd 集群的健康状况,评估性能瓶颈,辅助故障排查,支持集群的持续优化和技术决策。 其他说明:文档中大量使用了指标和术语,建议读者对 etcd、Go 语言、gRPC 和操作系统基础知识有一定的了解,以便更好地解读文档中的数据。对于不熟悉这些技术的读者来说,可能需要额外查阅相关资料来辅助理解。
Java编写的计算器程序是一种基于Java编程语言实现的计算工具,常用于教学或个人项目中,以帮助用户执行基本的数学运算。在这个简单的计算器程序中,我们可能会遇到以下几个关键的Java知识点: 1. **基础语法与控制结构**:Java的基础语法包括变量声明、数据类型(如int、double等)、条件语句(if-else)和循环语句(for, while)。在计算器程序中,这些元素用于读取用户输入、判断操作类型以及重复执行某些计算过程。 2. **面向对象编程**:Java是一种面向对象的语言,因此计算器程序可能包含多个类,如Calculator类、Button类(模拟图形界面的按钮)和Display类(显示计算结果)。类之间可能存在继承关系,例如Button类可能继承自一个抽象的UIComponent类。 3. **输入/输出处理**:在命令行计算器中,Java的Scanner类用于获取用户输入,如数字和运算符。在图形用户界面(GUI)计算器中,可能使用事件监听器处理用户的点击事件,获取按钮上的文字信息。 4. **异常处理**:为了确保程序的健壮性,计算器可能包含异常处理代码,比如当
SystemExit.md
NavigationGuardError解决办法.md