CLH锁其实是为了优化自旋锁的缺点:
所有自旋锁都在一个内存地址上自旋,持有锁的线程释放锁后,会引发惊群效应,而且会造成个别线程一只拿不到锁,处在饥饿状态,CLH锁通过队列将所有线程排队,避免惊群效应,也保证所有线程都能执行。
简单的自旋锁实现:
public class SpinLock {
private AtomicReference<Thread> owner = new AtomicReference<Thread>();
public boolean lock() throws InterruptedException {
Thread currThread = Thread.currentThread();
while (!owner.compareAndSet(null,currThread)){
System.out.println("本次未获得锁,继续自旋获取锁,thread name:"+currThread.getName());
Thread.sleep(1000);
}
System.out.println("获得锁成功,thread name:"+currThread.getName());
return true;
}
public boolean unlock() {
Thread currThread = Thread.currentThread();
if (owner.compareAndSet(currThread,null)){
System.out.println("释放锁成功,thread name:"+currThread.getName());
return true;
}else {
return false;
}
}
public static void main(String[] args) throws InterruptedException {
SpinLock lock = new SpinLock();
Thread t1 = new Thread(()->{
try {
if(lock.lock()){
System.out.println("获得锁,任务开始, t:"+ Thread.currentThread().getName());
Thread.sleep(1000);
lock.unlock();
System.out.println("任务结束,释放锁 t:"+ Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(()->{
try {
if(lock.lock()){
System.out.println("Get the SpinLock, t:"+ Thread.currentThread().getName());
Thread.sleep(1000);
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
CLH原理:
将线程包装为CLH节点,locked代表线程状态,false表示释放锁或未持有锁,true表示持有锁或正在排队等待锁。
@Data
private static class Node{
volatile boolean locked = false;
private Thread curThread;
private Node preNode;
}
获得锁:
1.设置一个尾节点tail,初始为一个空节点,节点状态为false;
2.新加入的线程直接到队尾排队,这一步通过CAS保证成功;
3.将前一个节点set进当前节点的属性;
4.自旋监听前一个节点的状态是否为释放锁;
5.获得锁;
释放锁:
1.将当前节点的状态设置为false,表示锁已释放
2.清空前一个节点,直接设置为null(help GC)
public class CLHLock {
@Data
private static class Node{
volatile boolean locked = false;
private Thread curThread;
private Node preNode;
}
AtomicReference<Node> tail =new AtomicReference(new Node());
ThreadLocal<Node> currNode = new ThreadLocal();
public boolean lock(Thread t){
Node currentNode = new Node();
currentNode.setCurThread(t);
Node preNode = tail.getAndSet(currentNode);
currentNode.setPreNode(preNode);
currentNode.setLocked(true);
while (currentNode.getPreNode().locked);
System.out.println("线程"+t.getName()+"获得锁");
currNode.set(currentNode);
return true;
}
public boolean unlock(Thread t){
Node currentNode = currNode.get();
if(null==currentNode){
return false;
}
currentNode.setLocked(false);
System.out.println("线程"+t.getName()+"释放锁成功");
currentNode.setPreNode(null);
//用完要释放,避免内存泄露
currNode.remove();
return true;
}
private static int counter = 0;
public static void main(String[] args) {
CLHLock clhLock = new CLHLock();
Runnable task = new Runnable() {
@Override
public void run() {
Thread t = Thread.currentThread();
if (clhLock.lock(t)) {
counter++;
System.out.println("线程" + t.getName() + "执行加法成功,counter:" + counter);
clhLock.unlock(t);
}
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
Thread t3 = new Thread(task);
Thread t4 = new Thread(task);
Thread t5 = new Thread(task);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}