AtomicInteger的使用

  1. AtomicInteger的应用
package com.today.service.financereport.generator.report

import java.util.concurrent.atomic.AtomicInteger

/**
  * running generator counter
  */
object Counter {
  /**
    * 同时正在导报表上限
    */
  var MAX_COUNT = new AtomicInteger(2)
  /**
    * 同时正在导报表下限
    */
  var MIN_COUNT = new AtomicInteger(0)

  var counter = new AtomicInteger

  def count: Int = {
    counter.intValue()
  }

  def increment = {
    counter.incrementAndGet() // 解决++i非原子性多线程下的并发问题
  }

  def decrement = {
    counter.decrementAndGet() // 解决--i非原子性多线程下的并发问题
  }
}

  1. AtomicInteger源码分析
/*
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

package java.util.concurrent.atomic;
import java.util.function.IntUnaryOperator;
import java.util.function.IntBinaryOperator;
import sun.misc.Unsafe;

/**
 * An {@code int} value that may be updated atomically.  See the
 * {@link java.util.concurrent.atomic} package specification for
 * description of the properties of atomic variables. An
 * {@code AtomicInteger} is used in applications such as atomically
 * incremented counters, and cannot be used as a replacement for an
 * {@link java.lang.Integer}. However, this class does extend
 * {@code Number} to allow uniform access by tools and utilities that
 * deal with numerically-based classes.
 *
 * @since 1.5
 * @author Doug Lea
*/
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

// -----------------------构造函数-----------------------------
// AtomicInteger内部声明了一个volatile修饰的变量value用来保存实际值
// 使用带参的构造函数会将入参赋值给value,无参构造器value默认值为0
    /**
     * Creates a new AtomicInteger with the given initial value.
     * 
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }

// --------------------自增函数----------------------------
// 可以看到自增函数中调用了Unsafe函数的getAndAddInt方法

    /**
     * 对应i++操作
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    /**
     * 对应i--操作
     * Atomically decrements by one the current value.
     *
     * @return the previous value
     */
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }

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

    /**
     * 对应--i操作
     * Atomically decrements by one the current value.
     *
     * @return the updated value
     */
    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }

}

  1. Unsafe的getAndAddInt
    Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。
    Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。
    通常我们最好也不要使用Unsafe类,除非有明确的目的,并且也要对它有深入的了解才行。
    通过反编译可以看到Unsafe的getAndInt方法的代码
/*
 * 其中getIntVolatile和compareAndSwapInt都是native方法
 * getIntVolatile是获取当前的期望值
 * compareAndSwapInt就是我们平时说的CAS(compare and swap),通过比较如果内存区的值没有改变,那么就用新值直接给该内存区赋值
 */
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
  int i;
  do
  {
    i = getIntVolatile(paramObject, paramLong);
  } while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
  return i;
}
  1. CAS中ABA问题的解决
    CAS也并非完美的,它会导致ABA问题,就是说,当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。比如在链表中。
    那么如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号都会变话,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。
    在JDK中提供了AtomicStampedReference类来解决这个问题,思路是一样的。这个类也维护了一个int类型的标记stamp,每次更新数据的时候顺带更新一下stamp。
package com.today.service.financereport.generator.report;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 类功能描述://TODO
 *
 * @author ZerlindaLi create at 2019/4/15 22:00
 * @version 1.0.0
 */
public class TestAtomicInteger {
    private final static AtomicStampedReference<Integer> MAX_COUNT = new AtomicStampedReference<Integer>(2,0);
    private final static AtomicStampedReference<Integer> MIN_COUNT = new AtomicStampedReference<Integer>(0,0);
    private static AtomicStampedReference<Integer> counter = new AtomicStampedReference<Integer>(0,0);
    public static void main(String [] args) {
        System.out.println(init());
        System.out.println(increment());
        System.out.println(increment());
        System.out.println(increment());
        System.out.println(decrement());
        System.out.println(decrement());
        System.out.println(init());
    }

    public static Integer init(){
        return counter.getReference(); // 得到当前引用值
    }

    /**
     * ++i操作
     * @return
     */
    public static Integer increment() {
        counter.compareAndSet(init(), init()+1, counter.getStamp(), counter.getStamp()+1);
        return counter.getReference();
    }

    /**
     * --i操作
     * @return
     */
    public static Integer decrement() {
        counter.compareAndSet(init(), init()-1, counter.getStamp(), counter.getStamp()+1);
        return counter.getReference();
    }
}

运行结果为
0
1
2
3
2
1
1

可以看到使用AtomicStampedReference进行compareAndSet的时候,除了要验证数据,还要验证时间戳。

如果数据一样,但是时间戳不一样,那么这个数据其实也被修改过了。
可以将decrement()方法分别改成如下来测试一下

    public static Integer decrement() {
        counter.compareAndSet(1, init()-1, counter.getStamp(), counter.getStamp()+1);
        return counter.getReference();
    }
    
    public static Integer decrement() {
        counter.compareAndSet(init(), init()-1, 2, counter.getStamp()+1);
        return counter.getReference();
    }

运行结果为:
0
1
2
3
3
3
3
5. 其他原子操作的类
java的并发原子包里面提供了很多可以进行原子操作的类,比如:
AtomicInteger
AtomicBoolean
AtomicLong
AtomicReference
等等,一共分为四类:原子更新基本类型(3个)、原子更新数组、原子更新引用和原子更新属性(字段)。、提供这些原子类的目的就是为了解决基本类型操作的非原子性导致在多线程并发情况下引发的问题

猜你喜欢

转载自blog.csdn.net/licheng989/article/details/89322515
今日推荐