之前我们粗浅的介绍了自旋锁(参见自旋锁浅析),这次主要介绍它的变种。
首先是可重入自旋锁。参照之前的实现代码,我们可以了解到,当一个线程第一次已经获取到了自旋锁,如果在锁释放之前又一次重新获取该锁,第二次就不能成功获取到。看例子:
@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毫秒.