JAVA并发-锁介绍2-SpinLock自旋锁
目录
当线程执行发生资源冲突时,一般有两种锁的解决方案,一种是挂起当前线程、调起其他线程,未获取到锁的线程将进入阻塞,另外一种是等待当前线程执行完毕。后面这一种就是本文将要讲到的自旋锁SpinLock。
SpinLock自旋锁定义
自旋锁是指在多线程环境中共享资源冲突情况下,线程反复执行检查锁变量是否可用的一种锁机制。
SpinLock简单示例
自旋锁
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
public AtomicReference<Thread> ar = new AtomicReference();
public void lock(){
Thread th = Thread.currentThread();
while(!ar.compareAndSet(null, th)){
System.out.println(th.getName()+" is Spinning!");
}
}
public void unlock(){
Thread th = Thread.currentThread();
if(ar.get()!=th){
System.out.println("Exception");
}
ar.set(null);
}
}
public class TestSpinLock {
static int count=0;
public static void main(String args[]) throws InterruptedException{
ExecutorService executors = Executors.newFixedThreadPool(100);
CountDownLatch countdown = new CountDownLatch(100);
SpinLock spinlock = new SpinLock();
for(int i =0;i<100;i++){
executors.execute(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
spinlock.lock();
count++;
spinlock.unlock();
countdown.countDown();
}
});
}
countdown.await();
System.out.print(count);
}
}
测试结果:
pool-1-thread-4 is Spinning!
pool-1-thread-5 is Spinning!
pool-1-thread-3 is Spinning!
pool-1-thread-1 is Spinning!
100
运行多次结果,其中输出自旋的线程号存在不同,但最后结果均为100,锁的功能基本实现。
SpinLock中CAS经典ABA问题
SpinLock实现过程中会用到CAS(Compare And Swap)方式,CAS有三个操作值,内存值M,预期值A,修改操作值B,如果M等于A,则将内存值修改为B。如果两次CAS正好是A->B->A,则CAS会认为未发生变化。针对这类问题,可以引入版本号,如1A->2B->3A。如果涉及到多个变量的原子操作可以考虑拼接多变量成一个字符串的方式。JDK1.5以后提供了类似版本号的这种方式AtomicStampedReference,如下所描述的这样,“An AtomicStampedReference maintains an object reference along with an integer “stamp”, that can be updated atomically”.
SpinLock死锁问题探讨
以本文的简单示例继续探讨SpinLock可能死锁的问题,先看以下代码:
public class TestSpinLockWithDeadLock implements Runnable{
public SpinLock lock;
public TestSpinLockWithDeadLock(SpinLock lock){
this.lock=lock;
}
public static void main(String args[]){
SpinLock spinlock = new SpinLock();
TestSpinLockWithDeadLock th= new TestSpinLockWithDeadLock(spinlock);
Thread t = new Thread(th);
t.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
this.lock.lock();;
this.lock.lock();
System.out.println("Running");
this.lock.unlock();
this.lock.unlock();
}
}
Console控制台中循环打印以下输出,无法停止下来。
Thread-0 is Spinning!
Thread-0 is Spinning!
Thread-0 is Spinning!
Thread-0 is Spinning!
Thread-0 is Spinning!
可见如果同一线程重复加锁会导致死锁问题,即不可重入,如果需要重入,需要在SpinLock中加入计数器进行if-else判断,是同一进程则进行自增自减处理,否则进行CAS操作。在Linux内核中如果同一CPU上某线程持有锁A,中断后中断上下文继续尝试获得该锁,则也会导致死锁。