php 自旋锁,自旋锁、排队自旋锁、MCS锁、CLH锁(转)
自旋锁(Spin lock)
转:http://coderbee.net/index.php/concurrent/20131115/577
自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。
简单的实现
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
private AtomicReference owner = new AtomicReference();
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。
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 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.isLocked ) {// step 3
}
}
}
public void unlock(MCSNode currentThread) {
if ( UPDATER.get( this ) == currentThread) {// 锁拥有者进行释放锁才有意义
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.isLocked = false;
currentThread.next = null;// for GC
}
}
}
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 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锁队列图示:
差异:
从代码实现来看,CLH比MCS要简单得多。
从自旋的条件来看,CLH是在本地变量上自旋,MCS是自旋在其他对象的属性。
从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。
CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。
注意:这里实现的锁都是独占的,且不能重入的。
php 自旋锁,自旋锁、排队自旋锁、MCS锁、CLH锁(转)相关推荐
- aqs clh java_Java并发编程:AQS对CLH锁的优化
自旋锁适用于锁占用时间短,即锁保护临界区很小的情景AQS的自旋锁详解>.它需要保证各缓存数据的一致性,这可能会导致性能问题.因为在多处理器机器上每个线程对应的处理器都对同一个变量进行读写,而每次 ...
- AQS基础——多图详解CLH锁的原理与实现
1 什么是自旋锁和互斥锁? 由于CLH锁是一种自旋锁,那么我们先来看看自旋锁是什么? 自旋锁说白了也是一种互斥锁,只不过没有抢到锁的线程会一直自旋等待锁的释放,处于busy-waiting的状态,此时 ...
- java让线程空转_Java锁:悲观/乐观/阻塞/自旋/公平锁/闭锁,锁消除CAS及synchronized的三种锁级别...
JAVA LOCK 大全 [TOC] 一.广义分类:乐观锁/悲观锁 1.1 乐观锁的实现CAS (Compare and Swap) 乐观锁适合低并发的情况,在高并发的情况下由于自旋,性能甚至可能悲观 ...
- 并发编程中常见的锁机制:乐观锁、悲观锁、CAS、自旋锁、互斥锁、读写锁
文章目录 乐观锁 VS 悲观锁 悲观锁 乐观锁 CAS CAS机制 ABA问题 CAS的优缺点 互斥锁 VS 自旋锁 互斥锁 自旋锁 对比及应用场景 读写锁 实现方式 读写锁 VS 互斥锁 乐观锁 V ...
- 关抢占 自旋锁_也说自旋锁
前言 随着SMP处理器核越来越多 自旋锁是一种简单的轻量级的锁机制,理论上来说自旋锁可以小到一位,该位表示锁的状态,获取锁的线程尝试使用原子指令比较并交换该位,当锁不可用时反复自旋.自旋锁忙等待的机制 ...
- 悲观锁、乐观锁、间隙锁、死锁、自旋锁等
悲观锁 在每一个数据被修改前认为会影响其他事务,持保守意见,对数据进行加锁,这就是悲观锁(Pessimistic Concurrency Control). 悲观锁主要分为共享锁(shared loc ...
- 1.6的锁优化(适应性自旋/锁粗化/锁削除/轻量级锁/偏向锁)
高效并发是JDK 1.6的一个重要主题,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋(Adaptive Spinning).锁削除(Lock Elimin ...
- c语言用户态锁使用,用户态自旋锁、读写自旋锁及互斥锁
1.自旋锁 自旋锁最多可能被一个可执行线程所持有.一个被征用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间).所以自旋锁不应该被长时间持有. 自旋锁是不可递归的! (1)自旋锁相关 ...
- 自旋锁、阻塞锁、可重入锁、悲观锁、乐观锁、读写锁、偏向所、轻量级锁、重量级锁、锁膨胀、对象锁和类锁
1.自旋锁 自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行.若线程依然不能获得锁,才会 ...
最新文章
- 简简单单实践ERD Commander2005破解Windows密码
- 公众平台服务号、订阅号、企业号的相关说明
- webpack4 入门配置研究
- Java 反射取类中类_Java反射机制(二):通过反射取得类的结构
- c++ 结构体中不同类型的初始值_Golang语言基础教程:结构体
- 【教程】Edraw Max使用教程:Edraw Max快速入门指南
- 全国火车高铁站及车次数据爬虫(内含100W+数据)
- 初学,这个报错怎么解决
- 360快捷方式右上角的软件更新图标实现
- JNCIE考试准备指南(ITAA 2014版)
- Windows server 2012修改输入法
- [BJDCTF2020]Mark loves cat详细解法与思路
- MySQL是怎样运行的
- kindeditor默认粘贴为无文本格式怎么实现配置
- matlab数据整周期截断,凯塞窗四谱线插值FFT的电力谐波分析方法
- 前后端分离Oauth2.0 - springsecurity + spring-authorization-server —授权码模式
- L1-8 牛的对称 (20 分)
- kali安装步骤失败 选择并安装软件_PhotoShop CC2015软件下载+安装详细步骤
- 在线flash文本的抓取方法
- 使用JMeter上传excel文件