Java 中什么是无锁编程?

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

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 方法实现方式如下:

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 方法实现方式如下:

   /**
     * 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 问题的解决很简单,只需要在每次修改的时候,加上版本号即可。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏武培轩的专栏

TCP和UDP的区别

TCP TCP(Transmission Control Protocol,传输控制协议)是面向连接的协议,也就是说,在收发数据前,必须和对方建立可靠的连接。 ...

2995
来自专栏用户2442861的专栏

【网络协议】TCP连接的建立和释放

转载请注明出处:http://blog.csdn.net/ns_code/article/details/29382883

1491
来自专栏Janti

基础巩固——你应该这么理解TCP的三次握手和四次挥手

网络传输层负责最底层的底层链路连接。两台主机之间进行互联,基于网线的物理硬件上的协议。在这个侧面,主机与主机之间只认得硬件mac编码。并不认识IP。

692
来自专栏Java架构师历程

socket、tcp、udp、http 的认识及区别

在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。 在传输层中有TCP协议与UDP协议。

1.9K3
来自专栏coderhuo

TCP连接建立、断开过程详解

TCP连接建立过程需要经过三次握,断开过程需要经过四次挥手,为什么? 有没有其他的连接建立、断开方式?

1112
来自专栏北京马哥教育

TCP恋爱史:三次握手和四次分手

TCP恋爱史:三次握手和四次分手 ---- 一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环。 以我们最近出现的 TCP协议非常重...

2653
来自专栏高性能服务器开发

从抓包的角度分析connect()函数的连接过程

这篇文章主要是从tcp连接建立的角度来分析客户端程序如何利用connect函数和服务端程序建立tcp连接的,了解connect函数在建立连接的过程中底层协议栈做...

1311
来自专栏zhisheng

计算机网络基础常考面试题精华总结

1、OSI,TCP/IP,五层协议的体系结构,以及各层协议 答:OSI分层 (7层):物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。 TCP/IP...

4147
来自专栏everhad

小知识 安卓线程和ui

多线程环境下的ui修改   开发过程中,经常需要开启新的线程,并且在其它线程中改变ui线程的ui对象的状态。Android设计出于性能考虑,ui对象为非线程安全...

19710
来自专栏一名合格java开发的自我修养

TCP连接中time_wait在开发中的影响-搜人以鱼不如授之以渔

  根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方socket将进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(M...

2024

扫码关注云+社区

领取腾讯云代金券