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;
}
}
网友回复