+
95
-

回答

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;
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是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋。

从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。

CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。

CLH适合CPU个数不多的计算机硬件架构上,MCS则适合拥有很多CPU的硬件架构上

CLH和MCS实现的自旋锁都是不可重入的

网友回复

我知道答案,我要回答