`

AtomicInteger实现机制

阅读更多
  • 问题背景

  最近在看LinkedBlockingQueue看到了其中的count使用AtomicInteger修饰,之前也看过AtomicInteger的一些解释,也是似懂非懂的,今天深入的了解了其实现方式,学到了很多东西。

  • 基础介绍

   要对AtomicInteger有一个深入的认识,就必须要了解一下悲观锁和乐观锁。cpu是时分复用的,也就是把cpu的时间片,分配给不同的线程/进程轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换。切换涉及到清空寄存器,缓存数据。然后重新加载新的thread所需数据。当一个线程被挂起时,加入到阻塞队列,在一定的时间或条件下,在通过notify(),notifyAll()唤醒回来。在某个资源不可用的时候,就将cpu让出,把当前等待线程切换为阻塞状态。等到资源(比如一个共享数据)可用了,那么就将线程唤醒,让他进入runnable状态等待cpu调度。这就是典型的悲观锁的实现。独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

  但是,由于在进程挂起和恢复执行过程中存在着很大的开销。当一个线程正在等待锁时,它不能做任何事,所以悲观锁有很大的缺点。举个例子,如果一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价就会非常的高。

   所以就有了乐观锁的概念,他的核心思路就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。在上面的例子中,某个线程可以不让出cpu,而是一直while循环,如果失败就重试,直到成功为止。所以,当数据争用不严重时,乐观锁效果更好。比如我们要说的AtomicInteger底层同步CAS就是一种乐观锁思想的应用。

  CAS就是Compare and Swap的意思,比较并操作。很多的cpu直接支持CAS指令。CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

   在CAS操作中,会出现ABA问题。就是如果V的值先由A变成B,再由B变成A,那么仍然认为是发生了变化,并需要重新执行算法中的步骤。有简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的。

  • AtomicInteger分析

  Atomic包下类的理解:

  Atomic包是Java.util.concurrent下的另一个专门为线程安全设计的Java包,包含多个原子操作类。这个包里面提供了一组原子变量类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。——  引自@chenzehe 的博客。

  先来看一下AtomicInteger中getAndIncrement()方法的实现:

复制代码
1 public final int getAndIncrement() {
2         for (;;) {
3             int current = get();
4             int next = current + 1;
5             if (compareAndSet(current, next))
6                 return current;
7         }
8 }
复制代码

  这个方法的做法为先获取到当前的 value 属性值,然后将 value 加 1,赋值给一个局部的 next 变量,然而,这两步都是非线程安全的,但是内部有一个死循环,不断去做compareAndSet操作,直到成功为止,也就是修改的根本在compareAndSet方法里面,compareAndSet()方法的代码如下:

1 public final boolean compareAndSet(int expect, int update) {
2         return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
3 }

  compareAndSet 传入的为执行方法时获取到的 value 属性值,next 为加 1 后的值, compareAndSet所做的为调用 Sun 的 UnSafe 的 compareAndSwapInt 方法来完成,此方法为 native 方法,compareAndSwapInt 基于的是CPU 的 CAS指令来实现的。所以基于 CAS 的操作可认为是无阻塞的,一个线程的失败或挂起不会引起其它线程也失败或挂起。并且由于 CAS 操作是 CPU 原语,所以性能比较好。

  下面以具体的例子分析下AtomicInteger的实现:

  计数器(Counter),采用Java里比较方便的锁机制synchronized关键字,初步的代码如下:

复制代码
 1 public class Counter {
 2     private int value;  
 3       
 4     public synchronized int getValue() {  
 5         return value;  
 6     }  
 7   
 8     public synchronized int increment() {  
 9         return ++value;  
10     }  
11   
12     public synchronized int decrement() {  
13         return --value;  
14     }  
15 }
复制代码

  synchronized关键字是基于阻塞的锁机制,也就是说当一个线程拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁,这也就我们前面所说的悲观锁。这里会有些问题:首先,如果被阻塞的线程优先级很高很重要怎么办?其次,如果获得锁的线程一直不释放锁怎么办?(这种情况是非常糟糕的)。还有一种情况,如果有大量的线程来竞争资源,那CPU将会花费大量的时间和资源来处理这些竞争(事实上CPU的主要工作并非这些),同时,还有可能出现一些例如死锁之类的情况,最后,其实锁机制是一种比较粗糙,粒度比较大的机制,相对于像计数器这样的需求有点儿过于笨重,因此,对于这种需求我们期待一种更合适、更高效的线程安全机制。

  下面我们就以模拟CAS机制来实现Counter的例子:

   CAS类:

复制代码
 1 public class SimpleCAS {
 2     private volatile int value;
 3     public synchronized int getValue(){
 4         return value;  
 5     } 
 6     public synchronized boolean comperaAndSwap(int expectedValue,int newValue){
 7         int oldValue = value;
 8         if(oldValue == expectedValue){
 9             value = newValue;
10             return true;
11         }else{
12             return false;
13         }
14     }
15 }
复制代码

  CASCounter类:

复制代码
 1 public class CASCounter {
 2     private SimpleCAS cas;  
 3     public int getValue(){
 4         return cas.getValue();
 5     }
 6     public int increment(){
 7         int olevalue = cas.getValue();
 8         for (; ;) {
 9             if(cas.comperaAndSwap(olevalue, olevalue+1)){
10                 return cas.getValue();
11             }
12         }
13          
14     }
15 }
复制代码

  上面的模拟不是CSA的真正实现,其实我们在语言层面是没有做任何同步的操作的,大家也可以看到源码没有任何锁加在上面,可它为什么是线程安全的呢?这就是Atomic包下这些类的奥秘:语言层面不做处理,我们将其交给硬件—CPU和内存,利用CPU的多处理能力,实现硬件层面的阻塞,再加上volatile变量的特性即可实现基于原子操作的线程安全。所以说,CAS并不是无阻塞,只是阻塞并非在语言、线程方面,而是在硬件层面,所以无疑这样的操作会更快更高效!

  总结一下,AtomicInteger基于冲突检测的乐观并发策略。 可以想象,这种乐观在线程数目非常多的情况下,失败的概率会指数型增加。

分享到:
评论

相关推荐

    ViewPager+AtomicInteger实现广告轮播

    本项目通过结合ViewPager和AtomicInteger实现了一个高效的广告轮播功能,具有自动轮播和响应用户触摸事件的能力。 首先,`ViewPager`是Android SDK提供的一种强大的组件,主要用于展示可滑动的页面集合。在这个案例...

    java并发之AtomicInteger源码分析

    AtomicInteger是Java并发包下面提供的原子类,主要操作的是int类型的整型,通过调用底层Unsafe的CAS等方法实现原子操作。下面是对AtomicInteger的源码分析。 1. 什么是原子操作? 原子操作是指不会被线程调度机制...

    Java中对AtomicInteger和int值在多线程下递增操作的测试

    这通常通过CAS(Compare and Swap)操作来实现,这是一种硬件支持的低级别原子操作。 相比之下,普通的`int`变量在多线程环境下进行修改时,如果不采取额外的同步措施(如`synchronized`关键字或`Lock`对象),可能...

    使用Java的Memory Model实现一个简单的计数器.txt

    2. **性能优势**:相比于使用锁机制(如`synchronized`),`AtomicInteger`通过CAS算法实现了更高的并发性能。这是因为锁机制在高并发场景下可能导致线程阻塞,而CAS则可以避免这种情况。 3. **简化代码**:使用`...

    Java并发——无锁实现

    在Java并发编程中,无锁实现是一个高级技术,它可以让多个线程在没有使用传统锁机制(如synchronized关键字或显示锁Lock)的情况下,安全地执行对共享资源的操作。无锁机制主要依赖于硬件的原子指令,尤其是比较并...

    Java 并行机制的核心

    监视器机制不仅实现了互斥访问,还允许线程之间通过`wait`和`notify`操作进行合作。 - **互斥访问**:监视器确保同一时刻只有一个线程可以执行某个对象的临界区代码。 - **等待与通知**:线程可以通过调用监视器中...

    java 使用ConcurrentHashMap和计数器实现锁

    Java 使用 ConcurrentHashMap 和计数器实现锁是 Java 编程语言中的一种常见的锁机制实现方式。该机制主要通过使用 ConcurrentHashMap 和计数器来实现线程之间的同步和排队。 ConcurrentHashMap 介绍 ...

    JAVA初级面试题(release)

    - **接口实现**:类可以实现多个接口,实现多继承的效果。 10. **异常处理**: - **try-catch-finally**:异常的捕获与处理。 - **throw与throws**:明确抛出异常和声明可能抛出的异常。 11. **集合框架**: -...

    JAVA CAS深度分析

    该包中的大多数类都使用 CAS 操作来实现同步机制,例如 AtomicInteger、AtomicLong 等原子变量类。 CAS 操作的原理可以通过分析 CPU 底层指令来理解。以 Intel x86 CPU 为例,CAS 操作可以通过 LOCK CMPXCHG 指令来...

    JAVA CAS实现原理与使用.docx

    这种机制是乐观锁的一种实现,因为它假设大多数情况下不会有冲突,即使有冲突也能快速解决。 CAS操作包含三个参数:内存位置V、预期值A和新值B。如果内存位置V的值等于预期值A,则将V的值设置为B,否则不做任何操作...

    Java分布式应用学习笔记03JVM对线程的资源同步和交互机制

    在深入探讨Java虚拟机(JVM)如何处理线程间的资源同步与交互机制之前,我们先来明确几个关键概念:线程、多线程、同步、并发以及它们在Java中的实现方式。Java作为一种广泛应用于分布式系统开发的编程语言,其内部...

    21-多线程和线程同步1

    AtomicInteger 类可以用于实现高效的线程同步机制。 等待和通知 等待和通知是多线程编程中的一种重要机制,用于让线程等待某个条件的出现或通知其他线程。Java 中提供了 wait() 方法和 notify() 方法来实现等待和...

    线程池顶层实现原理之线程模型,状态,执行流程,原理

    线程池顶层实现原理是指 Java 中的线程池实现机制,该机制是基于线程模型、线程状态和执行流程这三个核心概念。 线程模型 在 Java 中,有两种类型的线程模型:用户线程(UTL)和内核线程(KTL)。用户线程是由应用...

    探索Java并发的基石:同步机制的全面解析

    1. **Synchronized关键字**:这是最基本的同步机制之一,可以通过同步方法或者同步代码块的方式来实现。当一个线程获得了锁之后,其他试图获取相同锁的线程将会被阻塞,直到当前线程释放锁。 ```java public ...

    如何限流?在工作中是怎么做的?说一下具体的实现?java实现

    限流是一种服务降级机制,旨在限制系统的输入和输出流量,以达到保护系统的目的。下面将详细介绍限流的概念、实现方法和Java实现。 什么是限流 限流是服务降级的一种,旨在限制系统的输入和输出流量,以达到保护...

    基于Java+MySQL设计与实现的秒杀与抢购模型架构【100013279】

    3.基于AtomicInteger的CAS机制; 4.使用Redis作为原子计数器(watch事务+decr操作),RabbitMQ作为消息队列记录用户抢购行为,MySQL做异步存储。 上述四个解决方案均使用了JMeter进行压力与性能测试(实验设置的是10...

    Java Atomic类及线程同步新机制原理解析

    Java Atomic类是Java并发编程中的一种机制,用于实现原子操作。Atomic类提供了一种无锁的方式来实现线程安全的操作。Atomic类使用CAS(Compare-And-Swap)操作来实现原子操作。CAS操作是一种原子操作,用于比较并...

    基于Java的多线程网络爬虫设计与实现.pdf

    3. **多线程管理**:引入Synchronous关键字、原子数(AtomicInteger)和线程池等机制,确保多线程环境下的资源安全访问和高效调度。 - **Synchronous**:保证数据访问的同步,防止多个线程同时修改同一数据导致的...

    介绍了java中各种存在的锁机制、面试必备

    5. `Condition`与`synchronized`的wait/notify机制有何不同,如何使用`Condition`实现线程间的精确通信。 6. 介绍死锁、活锁和饥饿现象,以及如何避免它们。 7. 讨论锁的粒度对性能的影响,以及如何优化锁的使用。 ...

Global site tag (gtag.js) - Google Analytics