前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 中什么是无锁编程?

Java 中什么是无锁编程?

作者头像
水货程序员
修改2018-12-03 16:29:49
2.8K0
修改2018-12-03 16:29:49
举报
文章被收录于专栏:javathingsjavathings

多线程环境下,为了保证数据不受到并发操作的影响,通常会采用加锁的策略保证一致性。除了加锁之外,还有一种方式就是采用无锁编程。

Compare-and-Swap

Java 中的无锁编程本质上就是一个 CAS(compare-and-swap)机制。CAS 是一个原子性操作,目前大部分的 CPU 都支持 CAS 指令, 能够使其在硬件层面上提供原子性操作。在  Intel  处理器中,CAS 通过指令 cmpxchg 实现,该机制在修改某个内存值的时候,会先比较内存值是否和给定的数值一致,如果一致则修改,不一致则不修改。由于这几步动作是原子操作,所以不必担心并发问题。

原子操作

原子操作是指这个操作不会被打断,一旦开始,不会有任何线程去修改相关的内存,原子操作会独占这段资源。这个特性是由 CPU 硬件通过相应的指令所保证的,处理器可以通过总线锁,或者是缓存锁来实现原子操作。所以说原子操作在修改一个内存对象时,是不会被干扰的,所以不会有并发的问题。

Java 中的无锁类

Java.util.concurrent 中提供了一些实现的原子操作的类,包括:AtomicBoolean、AtomicInteger、AtomicIntegerArray、AtomicLong、AtomicReference、AtomicReferenceArray。

以 AtomicLong 为例,AtomicLong 中有一个原子自增方法 incrementAndGet。 在 jdk1.7 中,getAndIncrement 方法实现方式如下:

代码语言:javascript
复制
public final int getAndIncrement() {  
        for (;;) {  
            int current = get();  
            int next = current + 1;  
            if (compareAndSet(current, next))  
                return current;  
        }  
}  
   
public final boolean compareAndSet(int expect, int update) {  
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
}

代码中可以看到,getAndIncrement 方法中,死循环调用 compareAndSet 方法,如果 compareAndSet 返回失败就会一直重试,直到 compareAndSet 返回 true。其中 compareAndSet 方法用的 unsafe.compareAndSwapInt 方法,该方法就是调用 CPU 中的 CAS 指令。

在 jdk1.8 中,getAndIncrement 方法实现方式如下:

代码语言:javascript
复制
   /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final long getAndIncrement() {
        return unsafe.getAndAddLong(this, valueOffset, 1L);
    }

可以看到,直接使用了 Unsafe 类,Unsafe 类直接提供了硬件级别的原子操作。

CAS 的 ABA 问题

虽然 CAS 操作是原子性的,但是 CAS 操作时,需要提供某时刻内存中的数据用于比较,这个操作和 CAS 操作之间并不是原子的,有一段时间差,这中间可能导致 ABA 问题,即数据从 A 变成 B 又变成 A。

可能的事件序列:

线程 1 从内存位置 V 中取出 A。 线程 2 从位置 V 中取出 A。 线程 2 进行了一些操作,将 B 写入位置 V。 线程 2 将 A 再次写入位置 V。 线程 1 进行 CAS 操作,发现位置 V 中仍然是 A,操作成功。 尽管线程 1 的 CAS 操作成功,但不代表这个过程没有问题——对于线程 1 ,线程 2 的修改已经丢失。

一般来说,这个问题没太多影响,但是在某些场合下,还是会导致数据问题。毕竟数据曾经变过了。比如对一个基于链表实现的栈做 pop 和 push 操作,一定几率下出现问题,可以自行搜索 ABA 问题场景的例子。ABA 问题的解决很简单,只需要在每次修改的时候,加上版本号即可。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档