Java并发编程——Atomic基本类型

一. Atomic基本类型

1.基本数据类型

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean

2.常用方法

  • int get() // 获取当前值
  • int getAndSet(int value) // 获取当前值并设置成指定值等价于普通变量的i = value
  • int getAndIncrement() // 自增 ,等价于普通变量的i++
  • int getAndDecrement() // 自减,等价于普通变量的i–
  • int getAndAdd(int Value) // 加上预期值,等价于普通变量的i = i + value
  • boolean compareAndSet(int expect, int update) //如果该对象等于期望值,把它更新成update的值,等价于普通变量的`if (i == expect) {i = update}

二. AtomicInteger

  • AtomicInteger 底层采用的是CAS原理(CompareAndSwap)。AtomicInteger 类型的对象,之所以在对AtomicInteger对象操作时,能保证线程安全,是因为在进行操作时,会将当前对象的值与内存中变量的值进行比较,如果是一致的,则进行变量操作,否则继续去内存中获取变量的值,直至当前值,与内存中的值是一致的。
  • AtomicInteger 对象,可以通过调用incrementAndGet()方法和getAndIncrement()方法进行值加1的操作,区别在于:incrementAndGet()方法先执行加1操作,然后获取值;getAndIncrement()方法是先获取值然后执行加1的操作;

三. AtomicInteger 对象使用示例:

1.非线程安全的使用示例:

@Slf4j
public class CountExample1 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static int count = 0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count++;
    }
}

上述代码每次执行过程中,输出的count值都有可能会不相同,而且小于5000,这是因为count++操作时,会发生并发问题;

2. 使用AtomicInteger 实现线程安全的使用示例:

@Slf4j
public class AtomicExample1 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

	//创建AtomicInteger对象并初始化
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet(); //先增加后获取
//        count.getAndIncrement(); //先获取后增加
    }
}

3. AtomicInteger线程安全底层实现

上述AtomicInteger对象执行incrementAndGet()方法时,底层方法是:

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

其中getAndAddInt()方法的源码如下:

    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;
    }

其中compareAndSwapInt()方法是CAS机制中的一种,它的源码如下:

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

由于compareAndSwapInt()方法是被native关键字修饰,说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。这些函数的实现体在DLL中,JDK的源代码中并不包含,我们是看不到的。

getAndAddInt()方法执行过程如下这篇文章所示:getAndAddInt()执行过程


二、原子类的ABA问题

原因:

  • 所谓ABA就是一个线程在CAS更新变量的时候,该变量已经被别的线程先从A该成了B,再从B该回了A,此时线程还是会CAS成功,因为它认为期望值并没有变

解决办法:

  • 解决办法是增加版本号,每次修改版本号加1
  • 具体解决方法:使用版本号功能的类为AtomicStampedReference

Java并发编程学习系列

如有帮助,烦请点赞收藏一下啦 (◕ᴗ◕✿)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章