自旋锁
大约 3 分钟
自旋锁
1、自旋锁概念(spinlock)
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。
2、自旋锁的优点 :
自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)
3、自旋锁应用 :
由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。
如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。
4、简单自旋锁的实现 :
public class SimpleSpinLock {
/**
* 持有锁的线程,null表示锁未被线程持有
*/
private static AtomicReference<Thread> ref = new AtomicReference<>();
public void Lock() {
Thread currentThread = Thread.currentThread();
// 当ref为null的时候compareAndSet返回true,反之为false
// 通过循环不断的自旋判断锁是否被其他线程持有
while (!ref.compareAndSet(null, currentThread)) {
}
}
public void unLock() {
Thread currentThread = Thread.currentThread();
if (ref.get() != currentThread) {
}
ref.set(null);
}
}
test:
public class SimpleSpinLockTest {
private static int n = 0;
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(100, 100, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new DefaultNameThreadFactory("SimpleSpinLock"));
CountDownLatch countDownLatch = new CountDownLatch(100);
SimpleSpinLock simpleSpinLock = new SimpleSpinLock();
for (int i = 0; i < 100; i++) {
pool.submit(() -> {
simpleSpinLock.Lock();
n++;
simpleSpinLock.unLock();
// 计数减一
countDownLatch.countDown();
});
}
// 要求主线程等待所有任务全部准备好才一起并行执行
countDownLatch.await();
System.out.println(n);
}
}
5、可重入的自旋锁和不可重入的自旋锁 :
仔细分析一下上述就可以看出,它是不支持重入的,即当一个线程第一次已经获取到了该锁,在锁释放之前又一次重新获取该锁,第二次就不能成功获取到。
由于不满足CAS,所以第二次获取会进入while循环等待,而如果是可重入锁,第二次也是应该能够成功获取到的。为了实现可重入锁,我们需要引入一个计数器,用来记录获取锁的线程数----》其他章节可重入锁
6、 另有三种常见的形式 :
TicketLock ,CLHlock 和 MCSlock:https://www.cnblogs.com/stevenczp/p/7136416.html