mysql自旋锁次数_自旋锁学习系列(3):指数后退技术

上一篇中分析了测试锁的两种实现TASLock和TTASLock,主要对这两种锁的性能进行了分析。对于TTASLock,我们知道比TASLock性能上要好很多,具体分析已经讲过了。我们最后也说了,TTASLock虽然比TASLock大有改进,但是在性能上还是不够理想。这一篇的目的就是针对TTASLock做一下改进。

我们再来看一下TTASLock的实现源码和加锁的流程图:

/**

*

* Test test and set lock

*

*/

public class TTASLock {

private AtomicBoolean state = new AtomicBoolean(false);

// 加锁

public void lock() {

while (true) {

while (state.get()) {

// 自旋

}

if (!state.getAndSet(true)) {

break;

}

}

}

// 解锁

public void unlock() {

state.set(false);

}

} 加锁流程图如下:

39b18d3e5ceb1d762922a7c934ff485e.png

从上文我们知道,对于TTASLock锁,性能问题主要出现在解锁上。一旦一个已经获得锁的线程执行解锁操作。其他线程都会产生缓存缺失,将会由”本地自旋”转变为从共享服务器中去获取状态值。这会消耗大量的总线资源。所以,如果我们要对TTASLock改进的话,需要从这里去想办法。

这里我们就要一直成为指数后退的技术。“指数后退”名字听起来挺吓人的,但是原理上很简单,实现上也不是多复杂的事情。我举个例子大家都明白了。我们用迅雷账户登录的时候,如果网络断掉了。迅雷会重新尝试登录。第一次尝试可能是5秒钟以后。如果第一次尝试失败,第二次尝试就会在10秒钟以后登录。依次类推,失败的次数越多,尝试登录的延时时间就越长,成指数性“后退”。很简单吧。

迅雷掉线“指数后退”方式的重登陆。

24771c225e926e87e1723da8563c4ca8.png

这个和加锁有什么关系呢?

我们再来深入看一下TTASLock的加锁过程,一旦一个线程获取不到锁,它会一直的在本地自旋等待。如果有一百多个线程争锁,就有99个(有一个获得了锁)在本地自旋等待。而那个获得锁的线程一旦释放锁,这99个线程都会产生缓存缺失。一直总线风暴之后,还是只有一个线程能获得锁。其他的依然在本地不断的自旋等待。过程分析完了,发现什么问题了吗?问题在于既然每次只有一个线程获得锁,需要所有的线程同时自旋等待吗?我们难道不能让等待线程“指数后退”吗?

OK,理论到此为止。让我们按照这个思路来实现吧。既然指数后退,我们先实现这部分功能。我们用一个BackOff类来代表这个抽象。

public class Backoff {

// 需要对后退时间设置一个最大值和最小值

final int minDelay, maxDelay;

int limit;

final Random random;

public Backoff(int min, int max) {

maxDelay = max;

minDelay = min;

limit = minDelay;

random = new Random();

}

public void backoff() throws InterruptedException {

// 计算需要睡眠的时间

int delay = random.nextInt(limit);

// 重新计算limit,每次两倍增加。但最大等于maxDelay

limit = Math.min(maxDelay, 2 * limit);

// 当前线程睡眠

Thread.sleep(delay);

}

} 然后我们把它用来改造我们的TTASLock:

class BackoffLock {

private AtomicBoolean state = new AtomicBoolean(false);

private static final int MIN_DELAY = ...;

private static final int MAX_DELAY = ...;

// 加锁

public void lock() throws InterruptedException {

Backoff back = new Backoff(MIN_DELAY, MAX_DELAY);

while (true) {

while (state.get()) {

// 自旋

}

if (!state.getAndSet(true)) {

return;

}

//关键点,一旦获取锁失败,就指数后退

else {

back.backoff();

}

}

}

// 解锁

public void unlock() {

state.set(false);

}

}

上面实现和TTASLock相比就多了指数后退这一步操作。其他的都没有什么变化。BackoffLock性能上比TAS系列要好的多。因为它不会在同时(随机的妙用哦)存在大量的线程去争锁,不会造成总线风暴。BackoffLock最关键的地方是确定睡眠的最小值和最大值,这个需要大量的测试才能确定合适的值,而且这两个值对性能影响非常大。所以BackoffLock锁的可移植性不好,因为不同的机器对这两个值都有不同的要求。

正因为BackoffLock不是非常的完美,所以引出了我们下一个主角的出现:队列锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值