详解JUC之原子类使用及实现

前言

我在详解JUC之原子类概述这篇文章中介绍了一下原子操作和JUC包下的原子类的类型,现在我就来介绍一下这些原子类。

操作基本类型的原子类

操作基本类型的原子类有3个

  • AtomicInteger:操作int类型
  • AtomicLong:操作long类型
  • AtomicBoolean:操作boolean类型

这些操作基本类型数据的原子类的使用是非常简单的,你对基本类型数据的操作,在这些相应的类中可以找到一些相应的方法。比如说的i++++i,就分别对应着getAndIncrement()incrementAndGet()方法,还有像加法如i = i + 3就对应着addAndGet(int delta)方法。

这3个类的操作是类似的,只要你看得懂英文一眼就知道它们的方法是做什么的。需要再次强调的是,这些方法都是原子操作,类似于同步方法,但是没有使用synchronized关键字,也没有锁什么的,而是用CAS+volatile关键字实现原子操作的,关于这点,在后面我会介绍一下原子类的实现。如果还不知道原子操作是什么,我建议你先去看详解JUC之原子类概述,而且里面有原子类解决多线程安全问题的例子,在这里就不重复展示了。

下面我就简单地演示一下操作基本类型的原子类的一些方法

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.junit.Test;

public class Demo {

    @Test
    public void testAtomicInteger() {
        AtomicInteger i = new AtomicInteger(100);
        int val = i.getAndIncrement();
        System.out.println(val);// 100
        System.out.println(i.intValue());// 101

        val = i.addAndGet(9);
        System.out.println(val);// 110
    }

    @Test
    public void testAtomicLong() {
        AtomicLong l = new AtomicLong(100);
        long val = l.getAndIncrement();
        System.out.println(val);// 100
        System.out.println(l.intValue());// 101

        val = l.addAndGet(9);
        System.out.println(val);// 110
    }

    @Test
    public void testAtomicBoolean(){
        AtomicBoolean b = new AtomicBoolean(true);
        boolean val = b.getAndSet(false);
        System.out.println(val);//true
        val = b.get();
        System.out.println(val);//false
    }
}

实现原理

大家观察这些原子类,比如说AtomicInteger,它的名字包含了一个Integer,那为什么我还要说这是操作基本类型的呢,这明显是包装类呀。别急,点进AtomicInteger的源码看看你就知道,它里面持有一个int类型变量value这个你一定要记住,因为它的所有操作都是围绕着它的

private volatile int value;

同理AtomicLongAtomicBoolean也持有它们各自的对应的基本类型变量value,不行你就去看它们的源代码咯。还一点需要注意的是它们都被volatile关键字修饰了

在讲解它们的实现前,我们先要了解CAS和volatile是什么鬼,我就不在这详细讲这两个东西了,不然篇幅又要老长了,关于CAS可以看Java中CAS详解,关于volatile可以看java中volatile关键字的含义

鉴于它们3个类的实现差不多,所以我就以AtomicInteger类来讲解。

我就以int addAndGet(int delta)方法开始,这个方法的意思就是加一个数并获取结果,比如说value是3而delta是5则返回8,下面是这个方法的源码

/** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the updated value */
public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

可以看到只有一行代码,这其中有个很重要的东西就是unsafe,这个是sun.misc.Unsafe类型的实例,现在一定注意,这个Unsafe类在JUC包中被频繁使用到的,所以特别重要。

sun.misc.unsafe

这个类可以用来在任意内存地址位置处读写数据,另外,还支持一些CAS原子操作。可见,对于普通用户来说,使用起来还是比较危险的,一般只是给开发Java API的用,所以人家将它保护地好好的呢,不耍点手段是用不着的了,如果你真想用可以参看sun.misc.Unsafe类的使用

严格上来说Unsafe类它不是Java SE中真正的一部份,所以你尽管去Java官方的JDK API文档找,找得到算我输。虽然在Oracle那我没找到,不过我在DocJar: Search Open Source Java API找到了(记住这个网站,搜索Java API很方便)。

这里就是它的API文档http://www.docjar.com/docs/api/sun/misc/Unsafe.html,查看它里面的一些方法,你就可以发现里面有很多本地(native)方法。

到了这里我觉得没必要再深入专研下去了,人家都这么保护这个Unsafe类,我们还是别戳破它吧,知道它是干嘛的就好了。

Java 7和Java 8的AtomicInteger源码区别

再回到我们的AtomicInteger源码,为了方便看我再把int addAndGet(int delta)方法贴上来,可以看见这个方法中的唯一一句代码是unsafe调用了方法getAndAddInt并将方法的返回值(先get再add说明返回的是value,就像i++先使用i值再进行i+1)加上delta得到的值作为最终返回结果,看方法名我们就很容易理解它到底干了什么。

/** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the updated value */
public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

相应的int getAndAdd(int delta)你也应该能看懂,这个更简单,直接调用unsafegetAndAddInt方法,不就是先获取value值作为返回结果,再对valuedelta并将结果赋值回给value

/** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the previous value */
public final int getAndAdd(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta);
}

经过我上面的介绍你就应该知道我们是无法Unsafe的源码的,所以它的方法源码我们看不到。不过这样子会不会觉得很没意思,说好的源码分析呢。行,下面就给你看看在Java 7的AtomicInteger源码,下面是addAndGet方法

/** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the updated value */
public final int addAndGet(int delta) {
    for (;;) {
        //获取value值赋值给current
        int current = get();
        //将current值(这个可能不会等于value值,因为可能value值已经被其它线程修改)加上delta并赋值给next
        int next = current + delta;
        //比较并设置成功的话就返回结果next,不然又会再次循环
        if (compareAndSet(current, next))
            return next;
    }
}

呐呐呐,是不是多了几行代码,下面我就来解析一下这个方法

(1) 首先第一步获取value值,并赋值给currentget方法代码如下,只是“简单”地返回value

/** * Gets the current value. * * @return the current value */
public final int get() {
    return value;
}

这个其实不简单,还记得我说过valuevolatile关键字修饰了嘛,说明此时get方法获取到的value一定是最新的。

(2) 然后第二步是,将current加上delta并赋值给next,这里需要注意的是,此时的current可能不会等于value,因为value可能已经被其它线程修改。

(3)然后第三步就是最重要的一步,我们先来看一下compareAndSet方法

/** * 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 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);
}

可以看到这个方法用到了UnsafecompareAndSwapInt函数,看名字就知道它是一个CAS函数了。

这个方法的作用就是,如果当前AtomicInteger持有的value值等于expect,则将value值设置成update,并返回true,不然就什么都不做返回false。

用大白话来说就是,诶,value你被改过没有呀,没有的话我就把你变成update

再接下来,如果compareAndSet方法返回的是true,说明value并没有被其它线程所修改,说此时操作的结果是正确的,即current的值还是等于value的,所以current + delta的结果就是我们想要的,所以直接返回结果。如果是false话,二话不说开始下一次尝试,别忘记for (;;)哟。

至于为什么要用for (;;)而不是while(true),你可以看看为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?

关于自旋和乐观策略

关于上面addAndGet的实现,我们可以发现里面有个无限循环,不停地去尝试修改操作,直到修改成功,这就是自旋,是一种乐观的策略。就是我假设每次执行都没人跟我争,有人跟我争那我就再多尝试一次。而悲观的策略就是我认为一定会有人跟我争的,所以每次我要执行代码的时候我先关上门(上锁)不给别人进。

自旋操作呢有一个比较大的缺点就是,在并发程度高的情况下,可能一个线程尝试了好久都没成功,那你想咯,一个线程没有放弃CPU执行权在那里不断循环,不是很浪费CPU资源吗?

我在后续的文章还会介绍JUC中的锁(Lock),里面有一个概念叫自旋锁。Java中的锁有很多讲究,很有特点,你可以看Java锁的膨胀过程和优化 了解一下

总结操作基本类型的原子类

总结一下操作基本类型的原子类,它们的内部都维护者一个对应的基本类型的成员变量value,这个变量是被volatile关键字修饰的,这可以保证每次一个线程要使用它都会拿到最新的值。然后在进行一些原子操作的时候,需要依赖一个神秘人物Unsafe类(这家伙是一个隐藏很深的东西,我们只需要知道它是干嘛的就好了),这个类里面就有一些CAS函数,一些原子操作就是通过自旋方式,不断地使用CAS函数进行尝试直到达到自己的目的。

关于操作基本类型的原子类我就介绍到这里,可能你会觉得我介绍的不全,不过我觉得是够了,因为无论是在方法使用方面还是实现方面我都介绍了一两个典型的例子,而其它的也就是以此类推,都可以看懂的了,看不懂你找我。

其它原子类

至于操作其它类型的原子类,老实说,我本来想每种类型都写篇文章介绍一下他们的使用和实现的,不过我感觉很没必要,因为它们都是换汤不换药的,实现的套路都差不多,可能操作的类型不同所以功能不太同,但是看API的话你肯定可以知道它是干嘛的,比如说操作整型数组的原子类java.util.concurrent.atomic.AtomicIntegerArray有个方法int getAndSet(int i, int newValue)方法,看名字就能猜到,先获取索引为i的元素再将索引i的元素设置为newValue,真的是没什么好讲的。

大家还是自己去看看吧……

最后我想说的是(别嫌我啰嗦~),我写的文章里面都会引用到很多其它文章,希望你们看的时候能去了解一下我引用的其它文章,因为很多知识都是由另外很多相关知识作为基础搭建起来的,你不能绕过那些的东西学习这些知识,这样子只会看的云里雾里。

    原文作者:JUC
    原文地址: https://blog.csdn.net/TimHeath/article/details/71441008
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞