1.概念

对于普通锁:如果线程b去获得线程a已经持有的锁失败时,线程b会挂起(阻塞)。但是挂起线程和恢复线程操作都需要转入内核状态完成,如果线程a持有锁的时间特别短,那么 线程b不应该放弃CPU时间片,而应该在原地“自旋”等待。自旋锁是一种非阻塞锁。

锁的本质就是等待,等待有两种方式:

  1. 线程阻塞;
  2. 线程自旋;
2. 自旋锁问题
2.1 过多占据cup时间

如果持有锁线程迟迟不释放锁,则自旋状态线程则会过多占据cpu时间,应该限制自旋状态时间。

2.2死锁问题

试想一下,有一个线程连续两次试图获得自旋锁(比如在递归程序中),第一次这个线程获得了该锁,当第二次试图加锁的时候,检测到锁已被占用(其实是被自己占用),那么这时,线程会一直等待自己释放该锁,而不能继续执行,这样就引起了死锁。因此递归程序使用自旋锁应该遵循以下原则:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。

3.自旋锁的三种基本实现TASLock/TTASLock/BackoffLock

程序运行时:自旋锁+计时器+驱动类

  • TASLock
/**
 * Tast And Set Loc:测试-设置锁
 *
 * 自旋状态时,每次尝试获取锁,都采用了CAS(check and set)操作,
 * 不断的设置锁标志位—当锁标志位可用时,一个线程拿到锁,其他线程仍然自旋
 *
 * TODO 缓存一致性流量风暴
 *
 * fixme AtomicBoolean保存状态,使用其getAndSet()方法判断锁状态并尝试获取锁
 */
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 ?释放锁?
    }

    @Override
    public String toString() {
        return "TASLock";
    }
}
  • TTASLock
/**
 * TTASLock: Test Test And Set Lock
 *
 * 自旋状态在尝试获取锁时,进行两步骤:
 */
public class TTASLock implements Lock{
    private AtomicBoolean mutex=new AtomicBoolean(false);

    @Override
    public void lock() {
        while(true){
            //第一步:如果为FALSE退出循环,表示可以(可以,而非已经)获取锁,如果为true,则进入自旋状态
            //fixme get()在处理器内部高速缓存中操作,不会产生一致性流量
            while(mutex.get()){}
            //使用getAndSet方法尝试获取锁
            if(!mutex.getAndSet(true)){
                return;
            };
        }
    }

    @Override
    public void unlock() {
        mutex.set(false);
    }

    @Override
    public String toString() {
        return "TTASLock";
    }
}
  • BackoffLock
package concurrency;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 在TTAS的基础上增加了线程回退,降低了收的争用,在说高争用的情况下减少争用,提高性能
 */
public class BackoffLock implements Lock {
    private final int MIN_DELAY,MAX_DELAY;

    public BackoffLock(int min, int max) {
        this.MIN_DELAY = min;
        this.MAX_DELAY = max;
    }

    private AtomicBoolean mutex=new AtomicBoolean(false);

    @Override
    public void lock() {
        //增加回退对象,其实就是线程休眠
        Backoff backoff=new Backoff(MIN_DELAY,MAX_DELAY);
        while(true){
            //TTAS的第一步,比第二部速度快:检测可以获取锁
            while(mutex.get()){};
            //尝试获取锁
            if(!mutex.getAndSet(true)){
                return;
            }else{
                try{
                    backoff.backoff();//执行了一个时间不固定的sleep()
                }catch (InterruptedException e){
                }
            }
        }
    }

    @Override
    public void unlock() {
        mutex.set(false);
    }

    @Override
    public String toString() {
        return "BackoffLock";
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

测试类:计时类,驱动类

/**
 * 计时类
 */
public class TimeCost implements Lock {

    private final Lock lock;

    public TimeCost(Lock lock) {
        this.lock = lock;
    }

    @Override
    public void lock() {
        long start=System.nanoTime();
        lock.lock();
        long duration=System.nanoTime()-start;

        System.out.println(lock.toString()+"time cost is "+duration);
    }

    @Override
    public void unlock() {
        lock.unlock();
    }
}
  • 驱动类
public class TASLockMain {
    private static TimeCost timeCost=new TimeCost(new TTASLock());
//  private static TimeCost timeCost=new TimeCost(new TASLock());

    public static void func(){
        timeCost.lock();
        timeCost.unlock();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10000; i++) {
            Thread t=new Thread(()-> func());
            t.start();
        }

    }
}