自旋锁与可重入锁
在RocketMq的broker落盘消息的store代码中,append数据之前加了锁。默认是使用自旋锁。
putMessageLock.lock(); //spin or ReentrantLock ,depending on store config
那么,在mq这种超高并发的情况下,怎么保证线程安全且兼顾效率?
spinlock自旋锁
为什么叫自旋锁呢?
是因为这种获取锁的方式是当一个线程在获取锁时,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁。就像不断的在门口转圈,查看锁开了没。
好处是节省有上下文的切换。坏处是,如果竞争过多,会空耗cpu。
非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入软件内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。(线程被阻塞后便进入内核态,这样导致系统在用户态与内核态之间来回切换,会影响锁的性能)
可以这样理解,内核态相当于一个休息室,非自旋锁发现锁着,就到休息室去了,一会再从休息室过来。自旋锁就一直在门口转悠。
rocketmq中自旋锁的实现,可以拿来参考
public class PutMessageSpinLock {
//true: 可以获取锁, false : 已经被锁住了.
private AtomicBoolean putMessageSpinLock = new AtomicBoolean(true);
@Override
public void lock() {
boolean flag;
do {
flag = this.putMessageSpinLock.compareAndSet(true, false);
}
while (!flag);
}
@Override
public void unlock() {
this.putMessageSpinLock.compareAndSet(false, true);
}
}
原理就是不断的尝试compareAndSet,直到set成功。
可以看出这个实现是不可重入的。
ReentrantLock可重入锁
rocketmq直接使用jdk提供的ReentrantLock
ReentrantLock可以根据构造参数选择是公平锁还是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
RocketMq使用的是非公平锁。