无论是简单的非公平自旋锁还是公平的基于排队的自旋锁,由于执行线程均在同一个共享变量上自旋,申请和释放锁的时候必须对该共享变量进行修改,这将导致所有参与排队自旋锁操作的处理器的缓存变得无效。如果排队自旋锁竞争比较激烈的话,平凡的缓存同步操作会导致繁重的系统总线和内存的流量,从而大大降低了系统整体的性能。
所以需要有一种办法能够让执行线程不再在同一个共享变量上自旋,避免过高频率的缓存同步操作,于是CLH锁和MCS锁应用诞生了。
CLH锁
CLH锁其实就是一种基于逻辑队列非线程饥饿的一种自旋公平锁,由于是Craig、Landin 和 Hagersten三位大佬的发明,因此命名为CLH锁。
CLH锁原理
- 首先有一个尾节点指针,通过这个尾节点指针来构建等待线程的逻辑队列,因此能确保线程先到先服务的公平性,因此尾指针可以说是构建逻辑的桥梁;此外这个尾节点指针是原子引用类型,避免了多线程并发操作的线程安全性问题。
- 通过等待锁的每个线程在自己的某个变量上自旋等待,这个变量将由前一个线程写入。由于某个线程获取锁操作时总是通过尾节点指针获取到前一线程写入的变量,而尾节点指针又是原子引用类型,因此确保了这个变量获取出来总是线程安全的。
简单来说就是:tailNode一直指向最后一个线程的当前节点,而preNode指向前一个线程的curNode节点,获取锁的时候会判断前一个线程的locked标识是否为false,为false则自己获取锁,如果前一个节点的locked为true则自旋等待获取锁。
当由新节点插入的时候:新节点的preNode指向当前的tailNode指向的节点,而tailNode指向新插入的节点。
CLH锁代码实现
1 | public class CLHLock { |
MCS锁
MCS自旋锁是一种基于单向链表的高性能、公平的自旋锁,申请加锁的线程只需要在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大的减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。
原理
- 每个线程持有一个自己的node,node有一个locked属性,true表示等待获取锁,false表示可以获取到锁,并且持有下一个node(后继者)的引用(可能存在)。
- 线程在轮询自己node的locked状态,true表示锁被其他线程占用,等待获取锁,自旋。
- 线程释放锁的时候,修改后继者(nextNode)的locked属性,通知后继结束自旋。
代码实现
1 | public class MCSLock { |