自旋锁

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

实现

  1. 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();
        }

    }
}

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

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

  1. 回退算法

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

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

你可能感兴趣的:(自旋锁)