CLH自旋锁(公平)
作者:CLH:Craig,Landin and Hagersten。 锁的名称都来源于发明人的名字首字母
CLH自旋锁是一种基于隐式链表(节点里面没有next指针)的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。CLH队列锁的优点是空间复杂度低(如果有n个线程,L个锁,每个线程每次只获取一个锁,那么需要的存储空间是O(L+n),n个线程有n个。myNode,L个锁有L个tail),CLH的一种变体被应用在了JAVA并发框架中。 CLH在SMP系统结构下该法是非常有效的。但在NUMA系统结构下,每个线程有自己的内存,如果前趋结点的内存位置比较远,自旋判断前趋结点的locked域,性能将大打折扣。
示例代码:
import java.util.concurrent.atomic.AtomicReference;
public class HelloWorld {
public static void main(String[] args) throws InterruptedException {
CLHLock lock=new CLHLock();
Runnable runnable=new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+" 获得锁 ");
//前驱释放,do own work
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+" 释放锁 ");
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t1=new Thread(runnable,"线程1");
Thread t2=new Thread(runnable,"线程2");
Thread t3=new Thread(runnable,"线程3");
t1.start();
t2.start();
t3.start();
}
}
/**
* Created by qindongliang on 2018/8/5.
*/
class CLHLock {
class Node{
//false代表没人占用锁
volatile boolean locked=false;
}
//指向最后加入的线程
final AtomicReference<Node> tail=new AtomicReference<>(new Node());
//使用ThreadLocal保证每个线程副本内都有一个Node对象
final ThreadLocal<Node> current;
public CLHLock(){
//初始化当前节点的node
current=new ThreadLocal<Node>(){
@Override
protected Node initialValue() {
return new Node();
}
};
}
public void lock() throws InterruptedException {
//得到当前线程的Node节点
Node own=current.get();
//修改为true,代表当前线程需要获取锁
own.locked=true;
//设置当前线程去注册锁,注意在多线程下环境下,这个
//方法仍然能保持原子性,,并返回上一次的加锁节点(前驱节点)
Node preNode=tail.getAndSet(own);
//在前驱节点上自旋
while(preNode.locked){
System.out.println(Thread.currentThread().getName()+" 开始自旋.... ");
Thread.sleep(2000);
}
}
public void unlock(){
//当前线程如果释放锁,只要将占用状态改为false即可
//因为其他的线程会轮询自己,所以volatile布尔变量改变之后
//会保证下一个线程能立即看到变化,从而得到锁
current.get().locked=false;
}
}
MCS自旋锁(公平)
作者:MCS:John Mellor-Crummey and Michael Scott。 锁的名称都来源于发明人的名字首字母
MCS Spinlock是一种基于显式链表(节点里面拥有next指针)的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,由直接前驱负责通知其结束自旋(与CLH自旋锁不同的地方,不在轮询前驱的状态,而是由前驱主动通知),从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。而MCS是在自己的结点的locked域上自旋等待。正因为如此,它解决了CLH在NUMA系统架构中获取locked域状态内存过远的问题。
示例代码:
import java.util.concurrent.atomic.AtomicReference;CLH 对比 MCS
public class HelloWorld {
public static void main(String[] args) {
MCSLock lock=new MCSLock();
Runnable runnable=new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+" 获得锁 ");
//前驱释放,do own work
Thread.sleep(4000);
System.out.println(Thread.currentThread().getName()+" 释放锁 ");
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t1=new Thread(runnable,"线程1");
Thread t2=new Thread(runnable,"线程2");
Thread t3=new Thread(runnable,"线程3");
t1.start();
t2.start();
t3.start();
}
}
/**
* Created by qindongliang on 2018/8/5.
*/
class MCSLock {
class Node{
volatile Node next;//后继节点
//默认false
volatile boolean locked;
}
//指向最后加入的线程
final AtomicReference<MCSLock.Node> tail=new AtomicReference<>(null);
ThreadLocal<Node> current;
public MCSLock(){
//初始化当前节点的node
current=new ThreadLocal<MCSLock.Node>(){
@Override
protected MCSLock.Node initialValue() {
return new MCSLock.Node();
}
};
}
public void lock() throws InterruptedException {
//获取当前线程的Node
Node own=current.get();
//获取前驱节点
Node preNode=tail.getAndSet(own);
//如果前驱节点不为null,说明有线程已经占用
if(preNode!=null){
//设置当前节点为需要占用状态;
own.locked=true;
//把前面节点的next指向自己
preNode.next=own;
//在自己的节点上自旋等待前驱通知
while(own.locked){
System.out.println(Thread.currentThread().getName()+" 开始自旋.... ");
Thread.sleep(2000);
}
}
System.out.println(Thread.currentThread().getName()+" 获得了锁.... ");
}
public void unlock(){
//获取自己的节点
Node own=current.get();
//
if(own.next==null){
//判断是不是自身是不是最后一个线程
if(tail.compareAndSet(own,null)){
//是的话就结束
return;
}
//在判断过程中,又有线程进来
while (own.next==null){
}
}
//本身解锁,通知它的后继节点可以工作了,不用再自旋了
own.next.locked=false;
own.next=null;// for gc
}
}
从自旋的条件来看,CLH是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋。
从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。
CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。
CLH适合CPU个数不多的计算机硬件架构上,MCS则适合拥有很多CPU的硬件架构上
CLH和MCS实现的自旋锁都是不可重入的
网友回复
DLNA与UPnP的区别和不同?
苏超自建抢票app,通过先预约再抽签化解高并发抢票?
python如何让给电脑在局域网中伪装成电视接收手机的投屏图片视频播放?
如何结合python+js如何自己的视频编码与加密播放直播?
python如何在电脑上通过局域网将本地视频或m3u8视频投屏电视播放?
腾讯视频爱奇艺优酷vip电影电视剧视频如何通过python绕过vip收费直接观看?
有没有可免费观看全球电视台直播m3u8地址url的合集?
有没有实现观影自由的免vip影视苹果 CMS V10 API的可用url?
python如何实时检测电脑usb插入检测报警?
如何判断真人操作的鼠标移动直线轨迹与机器操作的轨迹?