概述
在程序开发中比较常见,所有的线程去争抢一个共同的资源时,为了避免出现超买
、超卖
等并发问题,此时需要对其进行加锁
和解锁
操作。
自旋锁
在Java源码中和各种框架中都广泛使用,其本质就是一个死循环
。让一个线程在这个循环中不断的循环,不继续向下执行其他代码,这种现象用比较文艺的词描述就是自旋
。
当通过CAS算法执行失败时,则一直死循环等待何时可以获取到锁。
当前一个线程释放锁成功后,后面的线程CAS算法比较和交换操作执行成功后,方可跳出循环。
如Java中sun.misc.Unsafe#getAndAddInt
的源码部分一样:
手写一个自旋锁
接下来自己手写一个自旋锁
,实现高并发环境下
对数据操作的可控性。
package spin;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReference;
/**
* 手写 “自旋锁”;使用CAS算法
*/
@Slf4j
public class SpinLockDo {
// 针对线程进行加锁
AtomicReference<Thread> atomicReference = new AtomicReference();
// 加锁操作
public void myLock(){
Thread thread = Thread.currentThread();
log.info(thread.getName()+"-->myLock");
// 模拟自旋
// 1、当thread初始为null时,才能设置成功返回true
// 2、当thread不是初始状态,则一直让其自旋
while (!atomicReference.compareAndSet(null,thread)){
// 1s打印一次
TimeUnit.SECONDS.sleep(1);
log.info(thread.getName()+"---自旋中....");
}
}
// 解锁操作、
public void myUnLock(){
Thread thread = Thread.currentThread();
log.info(thread.getName()+"-->myUnLock");
// 使用CAS,当thread不是初始,则将其设置为初始化的null状态值
atomicReference.compareAndSet(thread,null);
}
}
测试自旋锁
package spin;
import java.util.concurrent.TimeUnit;
public class SpinLockDemo {
public static void main(String[] args) throws InterruptedException {
SpinLockDo spinLockDo = new SpinLockDo();
new Thread(()->{
try {
spinLockDo.myLock();
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLockDo.myUnLock();
}
},"t1").start();
// 延迟2s
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
try {
spinLockDo.myLock();
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLockDo.myUnLock();
}
},"t2").start();
}
}
执行后的结果
从结果中可以看出:
1、线程t1和t2都能进入自定义自旋锁工具类中。
2、线程t1先执行,拿到锁后,使用CAS变更了对象的值;使得线程t2在CAS计算中一直为false,由于取反则自旋。
3、当线程t1执行释放锁操作后,线程t2采取CAS算法获取到锁,结束自旋。