AtomicInteger介绍

JDK1.5之后的java.util.concurrent.atomic包里,多了一批原子处理类。主要用于在高并发环境下的高效程序处理。

网上关于这个原理介绍的比较靠谱的一片文章是出自IBM工程师的一篇:

流行的原子

 

值得一看。

这里,我们来看看AtomicInteger是如何使用非阻塞算法来实现并发控制的。

AtomicInteger的关键域只有一下3个:

 

Java代码   收藏代码
  1. // setup to use Unsafe.compareAndSwapInt for updates  
  2. private static final Unsafe unsafe = Unsafe.getUnsafe();  
  3. private static final long valueOffset;  
  4. private volatile int value;  

 这里, unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。

valueOffset是用来记录value本身在内存的便宜地址的,这个记录,也主要是为了在更新操作在内存中找到value的位置,方便比较。

注意:value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操作时,当前线程可以拿到value最新的值(并发环境下,value可能已经被其他线程更新了)。

这里,我们以自增的代码为例,可以看到这个并发控制的核心算法:

 

Java代码   收藏代码
  1.    /** 
  2.     * Atomically increments by one the current value. 
  3.     * 
  4.     * @return the updated value 
  5.     */  
  6.    public final int incrementAndGet() {  
  7.        for (;;) {  
  8.            //这里可以拿到value的最新值  
  9.            int current = get();  
  10.            int next = current + 1;  
  11.            if (compareAndSet(current, next))  
  12.                return next;  
  13.        }  
  14.    }  
  15.   
  16.    public final boolean compareAndSet(int expect, int update) {  
  17.        //使用unsafe的native方法,实现高效的硬件级别CAS  
  18. return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
  19.    }  

 好了,看到这个代码,基本上就看到这个类的核心了。相对来说,其实这个类还是比较简单的。

----------------------------------------------------------------------------------------------


之前看了java8的longadder实现,最近又看到一篇文章介绍longadder实现的。其实现思路也是分段,最后需要get的时候,再进行sum计算。其核心思路就是减少并发,但之前老的Atomic,难道就没有提升的空间了吗?昨晚进行了一次测试。测试代码如下:

01/**
02* Atomically increments by one the current value.
03*
04*@return the updated value
05*/
06public final int incrementAndGet() {
07 
08  for(;;) {
09 
10   int current = get();
11 
12    int next = current + 1;
13 
14    if(compareAndSet(current, next))
15 
16    return next;
17 
18   }
19}

以incrementAndGet为例,在非常高的并发下,compareAndSet会很大概率失败,因此导致了此处cpu不断的自旋,对cpu资源的浪费

既然知道此地是高并发的瓶颈,有什么办法呢?

01public class AtomicBetter {
02 
03      AtomicInteger ai=new AtomicInteger();
04 
05      public int incrementAndGet() {
06 
07             for(;;) {
08 
09                    int current =ai.get();
10 
11                    int next = current + 1;
12 
13                    if(compareAndSet(current, next))
14 
15                           return next;
16 
17              }
18 
19       }
20 
21      /**
22 
23       *如果cas失败,线程park
24 
25       *@paramcurrent
26 
27       *@paramnext
28 
29       *@return
30 
31       */
32 
33      private boolean compareAndSet(intcurrent,intnext) {
34 
35             if(ai.compareAndSet(current, next)) {
36 
37                    return true;
38 
39              }else{
40 
41                     LockSupport.parkNanos(1);
42 
43                    return false;
44 
45              }
46 
47       }
48 
49}

很简单,当cas失败后,对线程park,减少多线程竞争导致的频繁cas失败,更进一步的导致cpu自旋,浪费cpu的运算能力。在4核虚拟机,Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz  linux 2.6.32,(注意,老版本的内核,不支持高的精度ns级) 进行测试,同样都起4个线程,每个线程里面对AtomicInteger进行5kw次的incrementAndGet。原生的AtomicInteger,耗时14232ms,进行了35870次上下文切换,总共87967770955次时钟周期。那prak 1ns下呢,耗时5195ms,进行了19779次上下文切换,总共36187480878次时钟周期,明显性能上比原生的AtomicInteger更好,那这个park多少合适呢?那就只有人肉测试了

parktime(ms)context-switchescycles
AtomicInteger1423235,87087,967,770,955
1ns519519,77936,187,480,878
10ns505020,22334,839,351,263
100ns523820,72437,250,431,417
125ns453647,47926,149,046,788
140ns4008100,02218,342,728,517
150ns3864110,72016,146,816,453
200ns3561125,69411,793,941,243
300ns3456127,07210,200,338,988
500ns3410132,1639,545,542,340
1us3376134,4639,125,973,290
5us3383122,7959,009,226,315
10us3367113,9308,905,263,507
100us339150,9258,359,532,733
500us345617,2258,096,303,146
1ms348610,9827,993,812,198
10ms34562,6007,845,610,195
100ms35551,0207,804,575,756
500ms38548227,814,209,077

 

alt

 

alt

 

alt

本机环境下,park 1ms下,相对耗时,cs次数来说是最好的。因此这种优化要达到最佳效果,还要看cpu的情况而定,不是一概而定的

两个问题:

1、cas失败对线程park的副作用是什么。

2、如果park的时间继续加大,那会是这么样的结果呢。

(全文完)如果您喜欢此文请点赞,分享,评论。

转发:http://ifeve.com/better_atomicinteger/

<think>嗯,用户让我介绍一下volatile关键字。好的,首先我要确定用户的基础,可能他正在学习Java并发或者遇到了多线程的问题。volatile在Java中确实是一个比较重要的概念,但有时候容易让人混淆。 首先,我需要从基础说起,解释volatile的作用。用户可能知道变量在多线程中的可见性问题,比如一个线程修改了变量,另一个线程可能看不到变化。volatile能保证可见性,这点要强调清楚。 然后,要提到volatile的另一个特性,禁止指令重排序。这可能涉及到JVM的内存模型,比如happens-before原则。需要举例说明,比如单例模式中的双重检查锁定,这时候volatile的作用就是防止对象初始化时的重排序问题。 接下来,用户可能想知道volatile和synchronized的区别。这时候需要比较两者的不同,比如synchronized保证原子性和可见性,而volatile只保证可见性和顺序性。但要注意,volatile不保证原子性,比如i++这样的操作,这时候需要用synchronized或者原子类。 还要考虑用户的实际应用场景,比如状态标志位。这种情况下使用volatile是合适的,因为只是简单的赋值操作,不涉及复合操作。但如果是需要原子性,比如计数器,volatile就不够了,这时候需要AtomicInteger。 另外,用户可能会误解volatile能解决所有并发问题,需要指出它的局限性。例如,虽然它能保证单次读写的原子性,但像自增这种复合操作还是需要同步机制。 可能用户遇到过缓存一致性问题,或者线程间通信的问题。这时候volatile的正确使用就显得重要了。需要给出代码示例,比如状态标志位的例子,说明如何正确使用volatile。 还要提到内存屏障的概念,解释volatile如何通过内存屏障来实现可见性和禁止重排序。但不需要深入底层细节,保持解释在应用层面即可。 最后,总结volatile的适用场景和注意事项,帮助用户做出正确的选择。比如在性能敏感的场景下,如果只需要可见性,volatile比synchronized更轻量,但如果有复合操作,就需要其他方式。 需要确保解释清晰,避免技术术语过多,但又要准确。可能用户需要的是快速理解和使用,所以重点放在应用场景和常见误区上。</think>以下是对`volatile`关键字的详细解析,采用分层结构说明其工作原理和适用场景: $$ \begin{array}{|c|c|} \hline \text{特性维度} & \text{技术描述} \\ \hline \end{array} $$ 一、底层原理 ```mermaid graph LR A[volatile变量] --> B[强制所有读写操作直接访问主内存] B --> C[禁止CPU缓存优化] C --> D[插入内存屏障] D --> E[保证可见性+禁止指令重排序] ``` 二、核心特性 1. **可见性保证** - 任何线程修改`volatile`变量后,其他线程**立即**可见新值 - 对比普通变量:修改后可能暂存于CPU缓存/寄存器,其他线程无法感知 2. **有序性约束** - 通过内存屏障(Memory Barrier)实现: ```java StoreStore屏障 | volatile写 | StoreLoad屏障 LoadLoad屏障 | volatile读 | LoadStore屏障 ``` - 禁止JVM和CPU进行指令重排序优化 三、典型应用场景 1. **状态标志位** ```java volatile boolean shutdownRequested; public void shutdown() { shutdownRequested = true; } public void doWork() { while(!shutdownRequested) { // 执行任务 } } ``` 2. **双重检查锁定单例模式** ```java class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); // volatile防止指令重排序 } } } return instance; } } ``` 四、与synchronized对比 | 特性 | volatile | synchronized | |---------------------|-------------------------------|-----------------------| | 原子性 | 仅保证单次读/写原子性 | 保证代码块原子性 | | 可见性 | 立即可见 | 释放锁时同步主内存 | | 阻塞特性 | 非阻塞 | 线程阻塞 | | 适用场景 | 单一状态变更 | 复合操作/临界区 | 五、使用注意事项 1. **不适用场景** - 自增操作(i++)等非原子复合操作 - 多变量关联的状态变更 2. **性能影响** - 每次访问直接读写主内存,比普通变量慢约20% - 比`synchronized`轻量,但过度使用仍会影响性能 六、内存语义(JMM层面) 1. **写操作语义** - 刷新该变量到主内存 - 使其他线程缓存失效 2. **读操作语义** - 从主内存重新加载最新值 - 后续操作可见该变量的最新状态 七、现代硬件支持 1. **MESI协议增强**:CPU通过缓存一致性协议自动维护volatile可见性 2. **内存模式优化**:x86架构下StoreLoad屏障开销较大(使用`lock`指令实现) 根据Java规范,volatile的正确使用需要满足以下条件: $$ \text{适用条件} = \begin{cases} \text{变量不参与不变式约束} \\ \text{变量状态独立于其他变量} \\ \text{变量访问不需要加锁} \end{cases} $$ 建议结合`java.util.concurrent.atomic`包中的原子类(如`AtomicInteger`)使用,在保证线程安全的同时获得更好的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值