自旋锁自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态 。
自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短 。
简单的实现
import JAVA.util.concurrent.atomic.AtomicReference;
public class SpinLock {
private AtomicReference<Thread> owner = new AtomicReference<Thread>();
public void lock() {
Thread currentThread = Thread.currentThread();
// 如果锁未被占用,则设置当前线程为锁的拥有者
while (!owner.compareAndSet(null, currentThread)) {
}
}
public void unlock() {
Thread currentThread = Thread.currentThread();
// 只有锁的拥有者才能释放锁
owner.compareAndSet(currentThread, null);
}
}
SimpleSpinLock里有一个owner属性持有锁当前拥有者的线程的引用,如果该引用为null,则表示锁未被占用,不为null则被占用 。
这里用AtomicReference是为了使用它的原子性的compareAndSet方法(CAS操作),解决了多线程并发操作导致数据不一致的问题,确保其他线程可以看到锁的真实状态 。
缺点
- CAS操作需要硬件的配合;
- 保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)的数据一致性,通讯开销很大,在多处理器系统上更严重;
- 没法保证公平性,不保证等待进程/线程按照FIFO顺序获得锁 。
当线程释放锁时,将服务号加1,这样下一个线程看到这个变化,就退出自旋 。
简单的实现
import java.util.concurrent.atomic.AtomicInteger;
public class TicketLock {
private AtomicInteger serviceNum = new AtomicInteger(); // 服务号
private AtomicInteger ticketNum = new AtomicInteger(); // 排队号
public int lock() {
// 首先原子性地获得一个排队号
int myTicketNum = ticketNum.getAndIncrement();
// 只要当前服务号不是自己的就不断轮询
while (serviceNum.get() != myTicketNum) {
}
return myTicketNum;
}
public void unlock(int myTicket) {
// 只有当前线程拥有者才能释放锁
int next = myTicket + 1;
serviceNum.compareAndSet(myTicket, next);
}
}
缺点
Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能 。
下面介绍的CLH锁和MCS锁都是为了解决这个问题的 。
MCS 来自于其发明人名字的首字母: John Mellor-Crummey和Michael Scott 。
CLH的发明人是:Craig,Landin and Hagersten 。
MCS锁MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销 。
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class MCSLock {
public static class MCSNode {
volatile MCSNode next;
volatile boolean isBlock = true; // 默认是在等待锁
}
volatile MCSNode queue;// 指向最后一个申请锁的MCSNode
private static final AtomicReferenceFieldUpdater UPDATER = AtomicReferenceFieldUpdater
.newUpdater(MCSLock.class, MCSNode.class, "queue");
public void lock(MCSNode currentThread) {
MCSNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1
if (predecessor != null) {
predecessor.next = currentThread;// step 2
while (currentThread.isBlock) {// step 3
}
}else { // 只有一个线程在使用锁,没有前驱来通知它,所以得自己标记自己为非阻塞
currentThread. isBlock = false;
}
}
public void unlock(MCSNode currentThread) {
if (currentThread.isBlock) {// 锁拥有者进行释放锁才有意义
return;
}
if (currentThread.next == null) {// 检查是否有人排在自己后面
if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4
// compareAndSet返回true表示确实没有人排在自己后面
return;
} else {
// 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
推荐阅读
- 一文读懂线性回归、岭回归和Lasso回归
- 冠军!恭喜南安石井古山小学、水头朴里小学!?
- 花茶、药枕 特色疗法治失眠
- 荷叶茶有减肥、降血压功效
- 高丽参的功效、高丽参的作用 高丽参作用
- 故宫、动物园、十三陵……景区陆续开放,这份攻略请收好!
- 手机忘记锁屏密码怎么办oppo oppo手机忘记密码怎么办怎么解锁
- 线程锁系列:CLH Lock
- CPU频率是什么?主频、睿频和超频哪个更重要?
- Linux :磁盘分区、挂载知多少?