自旋锁指的是线程在为获取到许可的情况下循环获取许可状态
实现
- TAS(Test And Set Lock)
public class TASLock implements Lock {
//初始值为false;
private AtomicBoolean mutex=new AtomicBoolean(false);
@Override
public void lock() {
//返回之前的值,并设置为true fixme 如果之前未true则进入自旋状态
//fixme mutex之前状态时FALSE时才返回,表示获取到锁
//原子变量的改动对所有线程都可见
while(mutex.getAndSet(true)){}
}
@Override
public void unlock() {
mutex.set(false);//fixme ?释放锁?
}
}
public class TASLockMain {
private static TASLock cost=new TASLock ();
public static void func(){
//自旋获取许可
cost.lock();
//释放许可
cost.unlock();
}
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
Thread t=new Thread(()-> func());
t.start();
}
}
}
不停的设置值会造成不停通知其他芯片值更改,产生缓存一致性风暴
- TTASLock(Test Test And Set Lock)
package com.test.lock;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 测试-测试-设置自旋锁,使用AtomicBoolean原子变量保存状态
* 分为两步来获取锁
* 1. 先采用读变量自旋的方式尝试获取锁
* 2. 当有可能获取锁时,再使用getAndSet原子操作来尝试获取锁
* 优点是第一步使用读变量的方式来获取锁,在处理器内部高速缓存操作,不会产生缓存一致性流量
* 缺点是当锁争用激烈的时候,第一步一直获取不到锁,getAndSet底层使用CAS来实现,一直在修改共享变量的值,会引发缓存一致性流量风暴
* **/
public class TTASLock implements Lock{
private AtomicBoolean mutex = new AtomicBoolean(false);
@Override
public void lock() {
while(true){
// 第一步使用读操作,尝试获取锁,当mutex为false时退出循环,表示可以获取锁
while(mutex.get()){}
// 第二部使用getAndSet方法来尝试获取锁
if(!mutex.getAndSet(true)){
return;
}
}
}
@Override
public void unlock() {
mutex.set(false);
}
public String toString(){
return "TTASLock";
}
}
先查看是否可用再设置,少了cas次数。但是在高征用的情况下会导致多次操作才能获取到锁,增加cas次数
- 回退算法
package com.test.lock;
import java.util.Random;
/**
* 回退算法,降低锁争用的几率
* **/
public class Backoff {
private final int minDelay, maxDelay;
private int limit;
final Random random;
public Backoff(int min, int max){
this.minDelay = min;
this.maxDelay = max;
limit = minDelay;
random = new Random();
}
// 回退,线程等待一段时间
public void backoff() throws InterruptedException{
int delay = random.nextInt(limit);
limit = Math.min(maxDelay, 2 * limit);
Thread.sleep(delay);
}
}
package com.test.lock;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 回退自旋锁,在测试-测试-设置自旋锁的基础上增加了线程回退,降低锁的争用
* 优点是在锁高争用的情况下减少了锁的争用,提高了执行的性能
* 缺点是回退的时间难以控制,需要不断测试才能找到合适的值,而且依赖底层硬件的性能,扩展性差
* **/
public class BackoffLock implements Lock{
private final int MIN_DELAY, MAX_DELAY;
public BackoffLock(int min, int max){
MIN_DELAY = min;
MAX_DELAY = max;
}
private AtomicBoolean mutex = new AtomicBoolean(false);
@Override
public void lock() {
// 增加回退对象
Backoff backoff = new Backoff(MIN_DELAY, MAX_DELAY);
while(true){
// 第一步使用读操作,尝试获取锁,当mutex为false时退出循环,表示可以获取锁
while(mutex.get()){}
// 第二部使用getAndSet方法来尝试获取锁
if(!mutex.getAndSet(true)){
return;
}else{
//回退
try {
backoff.backoff();
} catch (InterruptedException e) {
}
}
}
}
@Override
public void unlock() {
mutex.set(false);
}
public String toString(){
return "TTASLock";
}
}
获取失败后线程休眠一段时间,减少冲突概率。缺点是休眠时间不好设置,需要根据硬件条件调整参数。