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表示持有锁或正在排队等待锁。
@Dataprivate 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 {@Dataprivate 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);return true;}private static int counter = 0;public static void main(String[] args) {CLHLock clhLock = new CLHLock();Runnable task = new Runnable() {@Overridepublic 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();}
}