java concurrent

来源:互联网 发布:夏宽大师 淘宝 编辑:程序博客网 时间:2024/06/08 17:04

Java concurrent

java concurrent包是常用的jdk包之一,主要用于多线程编程,我认为可以主要由以下几部分构成:

  • 线程安全数据结构
  • 线程管理
  • 线程执行

目录

  • Java concurrent
    • 线程安全数据结构
      • CAS


线程安全数据结构

所谓线程安全的数据结构,即该数据结构的实例在多线程环境下运行时,可以保证每个线程可以正确的读和写该实例,而不会因为线程运行的时间不确定性导致该数据结构的值不可控。
为了达到这个目的,concurrent包里采用了两类方法实现

  • CAS(compare and swap)

接下来来看这两类实现方法的原理。

CAS

CAS又叫compare and swap, wiki,concurrent中使用这种方法实现的线程安全类有下面几个:

基于CAS的线程安全类 含义 AtomicBoolean 原子布尔 AtomicInteger 原子整形 AtomicIntegerArray 原子整形数组 AtomicLong 原子长整型 AtomicLongArray 原子长整型数组 AtomicReference 原子指针(需要有一个它指向对象的类型,源码中的V) AtomicReferenceArray 原子指针数组 AtomicStampedReference 原子标记指针(其实就是一个Pair(T reference, int stamp))

可以从命名上看出这些类均已Atomic开头,Atomic翻译过来是原子,所以以AtomicInteger为例,可以理解成为这个整形的操作都是原子操作。

如下是AtomicInteger的部分源码,

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;

这里的value就是我们平时使用Integer的value。这个变量一定需要声明成volatile的,原因是保证这个变量的值随时可被所有线程可见。volatile关键字的作用主要有两个,一个是保证变量的变化被所有线程可见(具体原因和java内存模型有关:jvm中的主存被所有线程共用,每个线程有自己的工作内存,共用内存的某些变量会在各个线程有一份拷贝,volatile是一个轻量级的同步操作,会将主存中变量的变化通知到各线程中,更改各线程中的拷贝);另一个是防止编译重排序。
如果我们和其他几个Atomic类比较会发现,他们都有一个unsafe 和offset的成员变量。

这里unsafe并不是线程不安全的意思,而是java要通过这个类执行非java语言实现的一些系统功能(比如compare and swap,大部分内核厂商提供了这样的方法接口),这些代码是危险的native方法,所以是unsafe。如注释所说,这个变量主要用于更新value。

offset是当前对象中value的偏移量,大小是这个value要代表的对象的大小,在这个类初始化的时候通过static方法块为其赋初值。

/**     * 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() {    }    /**     * Gets the current value.     *     * @return the current value     */    public final int get() {        return value;    }    /**     * Sets to the given value.     *     * @param newValue the new value     */    public final void set(int newValue) {        value = newValue;    }    /**     * Eventually sets to the given value.     *     * @param newValue the new value     * @since 1.6     */    public final void lazySet(int newValue) {        unsafe.putOrderedInt(this, valueOffset, newValue);    }    /**     * Atomically sets to the given value and returns the old value.     *     * @param newValue the new value     * @return the previous value     */    public final int getAndSet(int newValue) {        return unsafe.getAndSetInt(this, valueOffset, newValue);    }

AtomicInteger提供了构造方法为value赋初值,而且提供了针对这个value的get,set方法。另外两个很有意思的方法是getAndSet和lasySet。他们分别通过调用unsafe的getAndSetInt和putOrderedInt实现。这两个方法使用场景不多,getAndSet是可以原子的写入这个新值同时返回旧值。而lazySet是直接在对象的偏移地址写入新值,但这个新值因为延迟不会立即被其他线程可见,但终究会被其他线程可见。lazySet的性能要比直接set好,但除非真的有非常高的性能要求,否则不推荐使用,毕竟多线程编程安全性更重要。

/**     * Atomically sets the value to the given updated value     * if the current value {@code ==} the expected value.     *     * @param expect the expected value     * @param update the new value     * @return {@code true} if successful. False return indicates that     * the actual value was not equal to the expected value.     */    public final boolean compareAndSet(int expect, int update) {        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }    /**     * Atomically sets the value to the given updated value     * if the current value {@code ==} the expected value.     *     * <p><a href="package-summary.html#weakCompareAndSet">May fail     * spuriously and does not provide ordering guarantees</a>, so is     * only rarely an appropriate alternative to {@code compareAndSet}.     *     * @param expect the expected value     * @param update the new value     * @return {@code true} if successful     */    public final boolean weakCompareAndSet(int expect, int update) {        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }

然后就是我们最常用的compareAndSet,这个方法有两个输入expect和update,unsafe会比较当前value和expect的值,相等的话更新为update并返回true,不相等不更新并返回false。这里另外一个有意思的方法叫weakCompareAndSet,它的实现和compareAndSet一模一样但注释说这个方法可能失败。猜测应该是jdk以后会补全这个实现。

其余方法及说明如下表:

方法 说明 getAndIncrement() 返回旧值,并将其加一 getAndDecrement() 返回旧值,并将其减一 getAndAdd(int delta) 返回旧值,并将其加delta incrementAndGet() 将旧值加一,返回新值 decrementAndGet() 将旧值减一,返回新值 addAndGet(int delta) 将旧值加delta,返回新值 getAndUpdate(IntUnaryOperator updateFunction) 返回旧值,并将其执行一个IntUnaryOperator updateAndGet(IntUnaryOperator updateFunction) 将旧值执行一个IntUnaryOperator,返回新值 getAndAccumulate(int x,IntBinaryOperator accumulatorFunction) 返回旧值,并将其执行一个IntBinaryOperator,x为其操作数 accumulateAndGet(int x,IntBinaryOperator accumulatorFunction) 将旧值执行一个IntBinaryOperator,x为其操作数,并返回新值

这里的所有方法线程安全,其中前四个使用unsafe的native方法,后四个都调用compareAndSet进行更新操作,不成功的话会一直重试,直至get到的值compare成功。
这里有IntUnaryOperator和IntBinaryOperator可能造成困扰。IntUnaryOperator就是针对自身的一个计算操作,IntBinaryOperator为自身对另一个数的计算操作,如:

IntUnaryOperator i = (x) -> x*x;System.out.println(i.applyAsInt(2));4IntBinaryOperator io = (x,y)->x +y;System.out.println(io.applyAsInt(2,3));5

其他Atomic类和AtomicInteger类似,其中AtomicLong和AtomicInteger是基本一样的,而AtomicBoolean会更简单。而AtomicReference中,value变成一个可变类型,但这里有问题,后边会说。
AtomicIntegerArray中通过base和shift进行位运算得到每个数组下标的内存offset,而AtomicInteger中的value在这里替换成int[] array,值得注意的是这个数组不是volatile的,如果一个数组被volatile修饰,那么只能保护这个数组的引用,一旦引用指向的数组改变,这个保护就有问题了。一句话来说,针对数组来说volatile只能保护其引用,不能保护其元素。所以AtomicIntegerArray的方法都有一个i参数,表示要操作的元素下标。直接get时在getRow中通过unsafe.getIntVolatile(array, offset);方法保证单个元素的可见性。

CAS潜在的一些坑
CAS在一些情况下不能保证线程安全,比如常见的ABA场景:先获取了旧值A,假设A是一个对象,其中有一些状态。然后CAS(A,B),操作成功,这时当前值变成B,如果此时有其他线程修改了A的状态,然后CAS(B,A)。当前值变成A,这个A和之前的A引用是一样的,但是实际状态不同了。此时如果有其他线程再来做CAS(A,C),依旧会成功,但这显然是不正确的。AtomicReference就存在这个问题,这也是它不叫AtomicObject的原因,因为这里保护的只有引用。在concurrent里,AtomicStampedReference解决了这一问题,方法很巧妙,它把需要维护的reference和其时间戳做了一个pair放在一起,这样在compare的时候不仅要比较reference,还要比较时间戳,从而解决了ABA问题。