可重入的自旋锁
之前我们粗浅的介绍了自旋锁(参见自旋锁浅析),这次主要介绍它的变种。
首先是可重入自旋锁。参照之前的实现代码,我们可以了解到,当一个线程第一次已经获取到了自旋锁,如果在锁释放之前又一次重新获取该锁,第二次就不能成功获取到。看例子:
@Test public void testNotReentrant() { // 初始化自旋锁 SpinLock sl = new SpinLock(); // 第一次获取锁 sl.lock(); System.out.println("我来了."); // 第二次获取锁 sl.lock(); System.out.println("我又来了."); sl.unlock(); sl.unlock(); }
输出结果只有"我来了.",然后程序就卡死了。要让自旋锁支持可重入,其实也很简单,加入一个计数器而已。看实例:
新增一个自旋锁的实现类:
package com.wulf.test.testpilling.util; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; /** * 可重入自旋锁 * * @author wulf * @since 2018年12月25日*/ public class ReentrantSpinLock implements Lock { // 利用AtomicReference来调用CAS,ar初始(内存)值是null private AtomicReference<Thread> ar = new AtomicReference<Thread>(); private int lockCount = 0; @Override public void lock() { Thread currentThread = Thread.currentThread(); // 获取内存值,若已取到锁(初始值为null,当内存值也为null说明取到锁了),则计数器累加、退出方法 if (currentThread == ar.get()) { lockCount++; return; } // 取不到锁,继续转啊转 while (!ar.compareAndSet(null, currentThread)) { } } @Override public void unlock() { Thread currentThread = Thread.currentThread(); // 获取内存值,若已取到锁(初始值为null,当内存值也为null说明取到锁了),则计数器自减 if (currentThread == ar.get()) { if (lockCount > 0) { lockCount--; } else { // 只有计数器为0才能证明所有自旋锁已释放,这时才能真正放开锁,重置为null ar.compareAndSet(currentThread, null); } } } @Override public void lockInterruptibly() throws InterruptedException { // TODO Auto-generated method stub } @Override public boolean tryLock() { // TODO Auto-generated method stub return false; } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { // TODO Auto-generated method stub return false; } @Override public Condition newCondition() { // TODO Auto-generated method stub return null; } }
测试方法:
@Test public void testReentrant() { // 初始化自旋锁 ReentrantSpinLock rsl = new ReentrantSpinLock(); // 第一次获取锁 rsl.lock(); System.out.println("我来了."); // 第二次获取锁 rsl.lock(); System.out.println("我又来了."); rsl.unlock(); rsl.unlock(); }
这次输出结果对了,先打印"我来了."再打印"我又来了.",也不卡死了。这个是单线程,再看多线程:
@Test public void testReentrantSpinLock() { // 初始化自旋锁 ReentrantSpinLock rsl = new ReentrantSpinLock(); for (int i = 0; i < 10; i++) { new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 1000; j++) { // 加锁 rsl.lock(); // 自增 count++; // 解锁 rsl.unlock(); } // 一个线程执行完了就减1,10个线程执行完了就变成0,执行主线程 latch.countDown(); } }).start(); } // 主线程等待 try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } TestCase.assertEquals(count, 10000); }
输出:
count值:10000, 耗时:16毫秒.