6.JUC整理笔记六之Varhandle总结

了解 CAS

CAS (compare and swap),比较并交换,是原子操作的一种,属于 CPU 指令级别,可用于多线程编程中实现不被打断的数据交换,从而避免多线程同事改为某一个数据时由于执行顺序不确定以及中断的不可预知性产生的数据不一致问题。

该操作通过将内存中的值与指定值进行比较,当数值一样时将内存的数据替换为新的值。

在 Unsafe 和 Varhandle 中的描述,CAS 的内存含义与 getVolatile 和 setVolatile 一样。

应用

在应用中,可以用 CAS 来实现无锁的数据结构,如 atomic 包的原子操作类、ConcurrentHashMap 等等,可以说整个 JUC 都是基于在 CAS 的基础之上,使相对于悲观锁来说,性能上有着极大的提升。

ABA 问题

CAS 用起来很爽,但有个 ABA 问题,基本描述如下:

  • 线程 T1 读取一个数值 A
  • T1 被挂起(时间片耗尽、中断等等),进程 P2 开始执行
  • P2 修改数值 A 为数值 B,然后又修改回 A
  • P1 被唤醒,比较后发现数值 A 没有变化,程序继续执行

例子说明

public class AbaTest {
    @JCStressTest
    @Outcome(id = "1", expect = Expect.ACCEPTABLE)
    @Outcome(id = "2", expect = Expect.ACCEPTABLE)
    @Outcome(id = "3", expect = Expect.ACCEPTABLE)
    @State
    public static class AbaTester {
        private Node head;
        private static VarHandle HEAD_HANDLE;
        static {
            try {
                HEAD_HANDLE = MethodHandles.lookup().findVarHandle(AbaTester.class, "head", Node.class);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new Error(e);
            }
        }
        public AbaTester() {
            Node nodeB = new Node("B"null);
            head = new Node("A", nodeB);
        }
        @Actor
        public void actor1() {
            HEAD_HANDLE.compareAndSet(thisthis.head, this.head.next);
        }
        @Actor
        public void actor2() {
            Node oldHead = this.head;
            this.head = new Node("D"null);
            this.head = new Node("C"this.head);
            oldHead.next = this.head;
            this.head = oldHead;
        }
        @Arbiter
        public void arbiter(I_Result r) {
            int count = 0;
            Node current = this.head;
            do {
                count++;
                current = current.next;
            } while (current != null);
            r.r1 = count;
        }
    }
    private static class Node {
        private final String name;
        private volatile Node next;
        private Node(String name, Node next) {
            this.name = name;
            this.next = next;
        }
    }
}

案例说明

  • 当 actor1 先执行的时候,链表由 A->B 变成 B, 然后执行 actor2, 链表由 B, 变成 B->C-D; 链表长度为 3
  • 当 actor2 先执行的时候,链表由 A->B 变成 A->C->D, 然后执行 actor1, 链表由 A->C-D 变成 C->D; 链表长度为 2
  • 当链表长度变成 1 的时候,ABA 的问题就出现了。

ABA 问题:

若 actor1 先读取到 A 的时候,A.next 为 B,然后 actor1 被挂起了

到 actor2 执行完,head 依然是 A,但是 A.next 为 C 了,此时 actor1 被唤醒,然后把 head 设为 B,因为 B 的 next 为 null,所以链表长度为 1。

参考资料

代码地址:https://github.com/jfound/varhandle/blob/master/src/main/java/jfound/cas/AbaTest.java

JUC整理笔记三之测试工具jcstress

解决方式

常见实践:“版本号”的比对,一个数据一个版本,版本变化,即使值相同,也不应该修改成功。

普通操作

上篇文章,通过案例说明了普通变量、opaque、release/acquire、volatile 之间的区别

  • 普通变量是不确保内存可见的,opaque、release/acquire、volatile 是可以保证内存可见的
  • opaque 确保程序执行顺序,但不保证其它线程的可见顺序
  • release/acquire 保证程序执行顺序,setRelease 确保前面的 load 和 store 不会被重排序到后面,但不确保后面的 load 和 store 重排序到前面;getAcquire 确保后面的 load 和 store 不会被重排序到前面,但不确保前面的 load 和 store 被重排序。
  • volatile 确保程序执行顺序,能保证变量之间的不被重排序。

普通 CAS 基本操作

  • compareAndSet

原子 CAS 操作,内存语义和 getVolatile、setVolatile 一样。

  • compareAndExchange

原子 CAS 操作,内存语义和 getVolatile、setVolatile 一样,和 compareAndSet 不一样的是,该函数有返回值,返回的值是被替换的值,若替换成功,返回值和期望值是相等的。

  • compareAndExchangeAcquire

原子 CAS 操作,有返回被替换的值,内存语义和普通 set、getAcquire 一样。

  • compareAndExchangeRelease

原子 CAS 操作,有返回被替换的值,内存语义和 setRelease、 普通 get 一样。

weak CAS

weak 类 cas 和 cas 是一样的原理,不同的是,weak 尽管期望的数据和目标值是一样的,在并发情况下依然可能会失败。

参考资料:https://www.jianshu.com/p/55a66113bc54

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html#weakCompareAndSet

  • weakCompareAndSetPlain

原子 CAS 操作,内存语义和普通的 get、set 一样。

  • weakCompareAndSet

同 compareAndSet

  • weakCompareAndSetAcquire

同 compareAndSetAcquire

  • weakCompareAndSetRelease

同 compareAndExchangeRelease

赋值(set)

赋新值并返回旧值

  • getAndSet

内存语义和 getVolatile、setVolatile 一样

  • getAndSetAcquire

内存语义和普通 set、getAcquire 一样

  • getAndSetRelease

内存语义和 setRelease、 普通 get 一样

值加法(add)

值加法操作并返回加之间的制

  • getAndAdd

内存语义和 getVolatile、setVolatile 一样

  • getAndAddAcquire

内存语义和普通 set、getAcquire 一样

  • getAndAddRelease

内存语义和 setRelease、 普通 get 一样

bitwise

位运算并返回运算前的值

OR:或运算,只要运算中一方为 1,则结果为 1

XOR:异或运算,只要运算中一方为 1,一方为 0,则结果为 1,否则为 0

AND:与运算,运算中,必须双方都为 1,其结果才为 1,否则为 0

  • getAndBitwiseOr

or 运算,并返回运算前的值,内存语义和 getVolatile、setVolatile 一样

  • getAndBitwiseOrAcquire

or 运算,并返回运算前的值,内存语义和普通 set、getAcquire 一样

  • getAndBitwiseOrRelease

or 运算,并返回运算前的值,内存语义和 setRelease、 普通 get 一样

  • getAndBitwiseAnd

and 运算,并返回运算前的值,内存语义和 getVolatile、setVolatile 一样

  • getAndBitwiseAndAcquire

and 运算,并返回运算前的值,内存语义和普通 set、getAcquire 一样

  • getAndBitwiseAndRelease

and 运算,并返回运算前的值,内存语义和 getVolatile、setVolatile 一样

  • getAndBitwiseXor

xor 运算,并返回运算前的值,内存语义和 getVolatile、setVolatile 一样

  • getAndBitwiseXorAcquire

xor 运算,并返回运算前的值,内存语义和普通 set、getAcquire 一样

  • getAndBitwiseXorRelease

xor 运算,并返回运算前的值,内存语义和 getVolatile、setVolatile 一样


原文始发于微信公众号(JFound):6.JUC整理笔记六之Varhandle总结

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27743.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!