1、什么是自旋锁
自旋锁是为实现保护共享资源而提出一种锁机制,用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。它是一种非阻塞锁,也就是说,如果某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。
自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,这就是"自旋"。
2、Java是如何实现自旋锁?
手写demo实现自旋锁,
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
private AtomicReference<Thread> cas = new AtomicReference<Thread>();
public void lock() {
Thread current = Thread.currentThread();
while (!cas.compareAndSet(null, current)) { // 利用CAS
// 什么都不做,自旋
}
System.out.println(Thread.currentThread().getName()+"\t get lock");
}
public void unlock() {
Thread current = Thread.currentThread();
cas.compareAndSet(current, null);
System.out.println(Thread.currentThread().getName()+"\t release lock");
}
}
并测试代码,线程使用自旋方式获取锁,然后模拟2s的逻辑处理任务,
ExecutorService executorService = Executors.newFixedThreadPool(2);
final CountDownLatch countDownLatch = new CountDownLatch(2);
final SpinLock spinLock = new SpinLock();
for (int i = 0 ; i < 2 ; i++){
executorService.execute(new Runnable() {
@Override
public void run() {
spinLock.lock();
System.out.println(Thread.currentThread().getName()+"\t开始运行");
++count;
SystemClock.sleep(2000);
System.out.println(Thread.currentThread().getName()+"\t结束运行");
spinLock.unlock();
countDownLatch.countDown();
}
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
}
运行结果:
通过代码可以看出,自旋就是在循环判断条件是否满足,如果锁被占用很长时间的话,自旋的线程等待的时间也会变长,白白浪费掉处理器资源。因此在JDK中,自旋操作默认10次,我们可以通过参数“-XX:PreBlockSpin”来设置,当超过来此参数的值,则会使用传统的线程挂起方式来等待锁释放。
但是自旋锁也是有优点的,自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换,严重影响锁的性能。
3、自适应自旋锁
随着JDK的更新,在1.6的时候,出现了“自适应自旋锁”。所谓的“自适应”意味着对于同一个锁对象,线程的自旋时间是根据上一个持有该锁的线程的自旋时间以及状态来确定的。例如对于A锁对象来说,如果一个线程刚刚通过自旋获得到了锁,并且该线程也在运行中,那么JVM会认为此次自旋操作也是有很大的机会可以拿到锁,因此它会让自旋的时间相对延长。但是如果对于B锁对象自旋操作很少成功的话,JVM甚至可能直接忽略自旋操作。
因此,自适应自旋锁是一个更加智能,对我们的业务性能更加友好的一个锁。
4、JAVA自旋锁应用
Jdk 提供的java.util.concurrent.atomic包里面提供了一组原子类。 基本上就是当前获取锁的线程,执行更新的方法,其他线程自旋等待,比如atomicInteger类中的getAndAdd方法内部实际上使用的就是Unsafe的方法。