java concurrent

Java concurrent

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

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

目录


线程安全数据结构

所谓线程安全的数据结构,即该数据结构的实例在多线程环境下运行时,可以保证每个线程可以正确的读和写该实例,而不会因为线程运行的时间不确定性导致该数据结构的值不可控。
为了达到这个目的,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));
4
IntBinaryOperator 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问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值