前言
我在详解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;
同理AtomicLong
和AtomicBoolean
也持有它们各自的对应的基本类型变量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)
你也应该能看懂,这个更简单,直接调用unsafe
的getAndAddInt
方法,不就是先获取value
值作为返回结果,再对value
加delta
并将结果赋值回给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
值,并赋值给current
,get
方法代码如下,只是“简单”地返回value
/** * Gets the current value. * * @return the current value */
public final int get() {
return value;
}
这个其实不简单,还记得我说过value
被volatile
关键字修饰了嘛,说明此时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);
}
可以看到这个方法用到了Unsafe
的compareAndSwapInt
函数,看名字就知道它是一个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
,真的是没什么好讲的。
大家还是自己去看看吧……
最后我想说的是(别嫌我啰嗦~),我写的文章里面都会引用到很多其它文章,希望你们看的时候能去了解一下我引用的其它文章,因为很多知识都是由另外很多相关知识作为基础搭建起来的,你不能绕过那些的东西学习这些知识,这样子只会看的云里雾里。