程序员

java自旋锁

概念
一种锁,与互斥锁相似,基本作用是用于线程(进程)之间的同步。与普通锁不同的是,一个线程A在获得普通锁后,如果再有线程B试图获取锁,那么这个线程B将会挂起(阻塞);试想下,如果两个线程资源竞争不是特别激烈,而处理器阻塞一个线程引起的线程上下文的切换的代价高于等待资源的代价的时候(锁的已保持者保持锁时间比较短),那么线程B可以不放弃CPU时间片,而是在“原地”忙等,直到锁的持有者释放了该锁,这就是自旋锁的原理,可见自旋锁是一种非阻塞锁。
自旋锁可能引起的问题
1.过多占据CPU时间:如果锁的当前持有者长时间不释放该锁,那么等待者将长时间的占据cpu时间片,导致CPU资源的浪费,因此可以设定一个时间,当锁持有者超过这个时间不释放锁时,等待者会放弃CPU时间片阻塞;
2.死锁问题:试想一下,有一个线程连续两次试图获得自旋锁(比如在递归程序中),第一次这个线程获得了该锁,当第二次试图加锁的时候,检测到锁已被占用(其实是被自己占用),那么这时,线程会一直等待自己释放该锁,而不能继续执行,这样就引起了死锁。因此递归程序使用自旋锁应该遵循以下原则:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。
3。aba问题:java中自旋锁一般是利用CAS(compare And set)操作实现。

我们先来看一个多线程的运行场景:
时间点1 :线程1查询值是否为A
时间点2 :线程2查询值是否为A
时间点3 :线程2比较并更新值为B
时间点4 :线程2查询值是否为B
时间点5 :线程2比较并更新值为A
时间点6 :线程1比较并更新值为C
在这个线程执行场景中,2个线程交替执行。线程1在时间点6的时候依然能够正常的进行CAS操作,尽管在时间点2到时间点6期间已经发生一些意想不到的变化, 但是线程1对这些变化却一无所知,因为对线程1来说A的确还在。通常将这类现象称为ABA问题。ABA发生了,但线程不知道。又或者链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
ABA问题隐患
获取上面的描述ABA问题带来的隐患没有直观的认识,那我们来看下维基百科上面的形象描述:
你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候,把用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着手提箱去赶飞机去了。

4.自旋锁实现的原理
在java1.5版本及以上的并发框架java.util.concurrent 的atmoic包下的类基本都是自旋锁的实现,由于原理是CAS,所以是非阻塞的框架。

atmoic包下的类

先看第一个类AtomicBoolean

 private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
      try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicBoolean.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

这里是先实例化了Unsafe 这个类,然后得到了value属性的内存的虚拟地址valueOffset。Unsafe 这个类是java调用底层c的一些接口,由于java是安全的语言,所以这个类并没有文档,也不建议程序员使用,但是这个类非常有用,好多开源的底层框架都是基于他实现的(Netty、Hazelcast、Cassandra、Mockito / EasyMock / JMock / PowerMock、Scala Specs、Spock、Robolectric、Grails、Neo4j、Spring Framework、Akka、Apache Kafka、Apache Wink、Apache Storm、Apache Hadoop、Apache Continuum)。这里的objectFieldOffset方法

/**
 * Gets the raw byte offset from the start of an object's memory to
 * the memory used to store the indicated instance field.
 * @param field non-null; the field in question, which must be an
 * instance field
 * @return the offset to the field
 */

 public long objectFieldOffset(Field field) {

     if (Modifier.isStatic(field.getModifiers())) {

     throw new IllegalArgumentException(

     "valid for instance fields only");

     }
     return objectFieldOffset0(field);

 }

得到了内存虚拟地址(非物理地址),这里写了个测试类,
在使用Unsafe之前,我们需要创建Unsafe对象的实例。这并不像Unsafe unsafe = new Unsafe()
这么简单,因为Unsafe的
构造器是私有的。它也有一个静态的getUnsafe()
方法,但如果你直接调用Unsafe.getUnsafe()
,你可能会得到SecurityException异常。只能从受信任的代码中使用这个方法。
public static Unsafe getUnsafe() {
Class cc = sun.reflect.Reflection.getCallerClass(2);
if (cc.getClassLoader() != null)
throw new SecurityException(“Unsafe”);
return theUnsafe;
}

这就是Java如何验证代码是否可信。它只检查我们的代码是否由主要的类加载器加载。
我们可以令我们的代码“受信任”。运行程序时,使用bootclasspath 选项,指定系统类路径加上你使用的一个Unsafe路径。
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar: . com.alibaba.otter.canal.common.ObjectLocationTest
但这麻烦和困难。
Unsafe类包含一个私有的、名为theUnsafe的实例,我们可以通过Java反射窃取该变量。

theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeInstance.setAccessible(true);  
unsafe  = (Unsafe)  theUnsafeInstance.get(Unsafe.class);

完整测试类

package com.alibaba.otter.canal.common;
import java.lang.reflect.Field;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import sun.misc.Unsafe;
@SuppressWarnings("restriction")
public class ObjectLocationTest extends AbstractZkTest {
    @SuppressWarnings("unused")
    private static int apple = 10;  
    @SuppressWarnings("unused")
    private int orange = 10;  
    Unsafe unsafe  = null;
    @Before
    public void setUp() {
        Field theUnsafeInstance;
        try {
            theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafeInstance.setAccessible(true);  
            unsafe  = (Unsafe)  theUnsafeInstance.get(Unsafe.class);  
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }  

    }

    @After
    public void tearDown() {
    }

    @Test
    public void testUnsafe() {
        try {
            Field appleField = ObjectLocationTest.class.getDeclaredField("apple");
            System.out.println("Location of Apple: " + unsafe.staticFieldOffset(appleField));  

            Field orangeField = ObjectLocationTest.class.getDeclaredField("orange");  
            System.out.println("Location of Orange: " + unsafe.objectFieldOffset(orangeField));
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  

    }
}

直接运行junit方式即可。

由于value是通过volatile关键字修饰的,我们知道volatile是能保证原子性操作的并发性的。所以在AtomicBoolean的源码中get set方法都是直接return的,但是getAndSet方法由于是非原子性的 这里用了compareAndSet方法 此方法中调用的正是unsafe.compareAndSwapInt的方法

public final boolean getAndSet(boolean newValue) {
        for (;;) {
            boolean current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }
public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }
/**
 * Performs a compare-and-set operation on an int
 * field within the given object.
 * @param obj non-null; object containing the field
 * @param offset offset to the field within obj
 * @param expectedValue expected value of the field
 * @param newValue new value to store in the field if the contents are
 * as expected
 * @return true if the new value was in fact stored, and
 * false if not
 */
 public native boolean compareAndSwapInt(Object obj, long offset,

 int expectedValue, int newValue);

还有weakCompareAndSet、lazySet 方法也是通过unsafe的unsafe.compareAndSwapInt和unsafe.putOrderedInt实现的

在看AtomicInteger类其实和AtomicBoolean基本一致 只不过增加了一些方法AtomicReference、AtomicLong这几个应该是属于一类的 都结合了volatile关键字。
AtomicIntegerArray、AtomicLongArray 、AtomicReferenceArray 都是原子更新引用类型数组里的元素。
AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 可以对volatie属性进行原子更新,利用的是反射。
AtomicMarkableReference 和AtomicStampedReference 是为了解决上面说的ABA问题提供的类
AtomicMarkableReference相当于一个[引用,integer]的二元组,AtomicStampedReference 相当于一个[引用,boolean]的二元组。
AtomicStampedReference可用来作为带版本号的原子引用,而AtomicMarkableReference可用于表示如:已删除的节点。

最后附上简单的java自旋锁实现

public class SpinLock {

  private AtomicReference sign =new AtomicReference<>();

  public void lock(){
    Thread current = Thread.currentThread();
    while(!sign .compareAndSet(null, current)){
    }
  }

  public void unlock (){
    Thread current = Thread.currentThread();
    sign .compareAndSet(current, null);
  }
}