AtomicInteger 类

2020-01-10  本文已影响0人  gczxbb

自增操作符(++),非原子性,线程不安全。线程安全的计数采用 synchronized 或 AtomicInteger 类。

AtomicInteger 类的 getAndIncrement() 方法实现原子操作,基于cas算法,非阻塞同步。

CAS算法

悲观锁,
每次请求数据时,都会认为其他线程修改,每次都会上锁,等用完后,解锁让其他线程使用,synchronized 和ReentrantLock 都是悲观锁。

乐观锁,
比较乐观的认为其他线程不会修改数据,不会上锁,在更新时,会判断有无其他线程更新数据,版本号机制或CAS算法实现,当线程拿到数据时,同时拿到version,更新数据且version++,写回时,发现另一个线程已经改新了数据,并提高了version,则会更新失败的,避免两个线程操作数据相互覆盖。

CAS算法
比较与交换,是一种非阻塞同步,读写的内存V,比较的值A,新值B,多个线程并发操作时,只V的值是A,才会改成B,这一套比较并修改是原子操作,其他线程发现已经是B了,不是A,不修改,会自旋。
在FutureTask中经常见到compareAndSwapInt。

算法问题,ABA问题,自旋,cpu开销,只能保证一个共享变量的原子操作。
ABA问题,
比较初始值A检查时,是A,没问题,可能中间被改动过,又改回了A,会认为没改过。AtomicStampedReference解决,控制变量值的版本解决。
乐观锁在读比较多时适用。atomic。synchronized使用写多场景。

i++,线程先取i值,然后+1,最后存入,一个线程未存入前,另一个线程取到原值,两个线程i++写入对值将相同,两次自增,算作一次。

int count=0;
    for (int i = 0; i < 10; i++) {
        new Thread() {
            public void run() {
                for (int j = 0; j < 1000; j++) {
                    count++;
                    atomicInteger.getAndIncrement();
                    countVolatile++;
                }
            }
        }.start();
    }

10个线程并发操作count变量,主线程中定义,初始值是0,对count操作时,拷贝副本到线程工作内存,加1,存入主内存。如果是volatile修饰:线程的每次操作count都会有这些步骤,使用时都取新的,保证使用其他线程对count的修改的最新值,更新后,都会写入主内存,确保其他线程可见。
如果没有volatile修饰,线程连续多次操作count时,不会每次都写入或读取最新,估计连续很多次做一次读写操作。总之,count的自增操作不是原子性。
当一个线程读,还未写入时,另一个线程进入,获取主线程当旧值,两个线程当操作是在相同值的基础上进行,如果都只加一次就写入,少算一次自增。volatile可保证可见性,不能保证原子性。最后的值将小于10000。

 count:9252
 AtomicInteget count:10000
 countVolatile:9027

非阻塞同步

AtomicInteger 类的 getAndIncrement() 方法实现原理是非阻塞同步。

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

内部使用Unsafe类。

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
}

每次 getAndAddInt() 调用,都会有一个 while 循环,取var2位置的值var5,
compareAndSwapInt是native方法,原子性。当调用它时,会比较当前var2位置是否是var5,如果还是var5,说明其他线程未更新,将var5+var4,var4就是1,更新后当值写入var2位置。该操作是原子操作,保证线程安全。

如果这时已经不是var5,说明有线程已经更新值,返回false,继续循环,再去取值,这个过程并没有线程休眠,因此是非阻塞并发。


任重而道远

上一篇下一篇

猜你喜欢

热点阅读