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 <code>int</code>
 * field within the given object.
 * @param obj non-null; object containing the field
 * @param offset offset to the field within <code>obj</code>
 * @param expectedValue expected value of the field
 * @param newValue new value to store in the field if the contents are
 * as expected
 * @return <code>true</code> if the new value was in fact stored, and
 * <code>false</code> 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<Thread> 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);
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容