1.概念
对于普通锁:如果线程b去获得线程a已经持有的锁失败时,线程b会挂起(阻塞)。但是挂起线程和恢复线程操作都需要转入内核状态完成,如果线程a持有锁的时间特别短,那么 线程b不应该放弃CPU时间片,而应该在原地“自旋”等待。自旋锁是一种非阻塞锁。
锁的本质就是等待,等待有两种方式:
- 线程阻塞;
- 线程自旋;
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();
}
}
}