0%

自旋锁

文章字数:658,阅读全文大约需要2分钟

自旋锁指的是线程在为获取到许可的情况下循环获取许可状态

实现

  1. TAS(Test And Set Lock)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 ?释放锁?
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
}

}
}

不停的设置值会造成不停通知其他芯片值更改,产生缓存一致性风暴

  1. TTASLock(Test Test And Set Lock)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

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次数

  1. 回退算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

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";
    }
}

获取失败后线程休眠一段时间,减少冲突概率。缺点是休眠时间不好设置,需要根据硬件条件调整参数。