突击并发编程JUC系列演示代码地址:
https://github.com/mtcarpenter/JavaTutorial
Java
从JDK 1.5
开始提供了java.util.concurrent.atomic
包(以下简称Atomic
包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。原子类通过 CAS (compare and swap)
、 volatile
和native
方法实现,比 synchronized
开销更小,执行效率更高,在多线程环境下,无锁的进行原子操作。
Atomic包分类
基本类型
基本类型有AtomicBoolean
、 AtomicInteger
、AtomicLong
、这 3 个类提供的方法几乎一模一样,本章节以 AtomicLong
为案例进行讲解,提前小剧透为了在后面和LongAdder
进行对比,LongAdder
和 AtomicLong
在面试中也被问到过呢。
AtomicLong
的常用方法如下
方法名 | 说明 |
---|---|
long getAndIncrement() |
以原子方式将当前值加1,注意,返回的是旧值。(i++) |
long incrementAndGet() |
以原子方式将当前值加1,注意,返回的是新值。(++i) |
long getAndDecrement() |
以原子方式将当前值减 1,注意,返回的是旧值 。(i–) |
long decrementAndGet() |
以原子方式将当前值减 1,注意,返回的是旧值 。(–i) |
long addAndGet(int delta) |
以原子方式将输入的数值与实例中的值(AtomicLong 里的value )相加,并返回结果 |
long getAndSet(int newValue) |
以原子方式设置为newValue 的值,并返回旧值 |
long get() |
_获取 AtomicLong 中的值(value)_ |
boolean compareAndSet(int expect,int update) |
如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。 |
void lazySet(int newValue) |
最终会设置成newValue ,使用lazySet 设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 |
… | … |
JDK 1.8 新增 |
|
long getAndUpdate(LongUnaryOperator updateFunction) |
定函数的结果原子更新当前值,返回上一个值。 |
long updateAndGet(LongUnaryOperator updateFunction) |
使用给定函数的结果原子更新当前值,返回更新的值。 该功能应该是无副作用的,因为尝试的更新由于线程之间的争用而失败时可能会被重新应用。 |
… | … |
温馨提示:
i++
、++i
、i--
、--i
只是为了帮助大家理解、理解、理解,重要的事情说三遍,并不是底层的实现就是它们哟。
小试牛刀
古人云“是骡子是马拉出来溜溜“,一段代码撸起来,走你。
public class AtomicExample1 {
/**
* 初始化为 0
*/
private static AtomicLong count = new AtomicLong(0);
private static LongUnaryOperator longUnaryOperator = new LongUnaryOperator() {
@Override
public long applyAsLong(long operand) {
return 1;
}
};
private static LongBinaryOperator longBinaryOperator = new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
return left + right;
}
};
public static void main(String[] args) {
// 以原子方式将当前值加1,返回旧值 (i++): 0
System.out.println("getAndIncrement=" + count.getAndIncrement());
// 以原子方式将当前值加1,返回新值(++i) 两次增加 : 2
System.out.println("incrementAndGet=" + count.incrementAndGet());
//以原子方式将当前值减少 1,返回旧值 (i--):2
System.out.println("incrementAndGet=" + count.getAndDecrement());
//以原子方式将当前值减少 1,返回旧值 (--i):0
System.out.println("incrementAndGet=" + count.decrementAndGet());
// 以原子方式将输入的数值与实例中的值(AtomicLong里的value)相加,并返回结果
System.out.println("addAndGet=" + count.addAndGet(10));
// 以原子方式设置为`newValue`的值,并返回旧值
System.out.println("getAndSet=" + count.getAndSet(100));
// 获取 atomicLong 的 value
System.out.println("get=" + count.get());
System.out.println("*********** JDK 1.8 ***********");
// 使用将给定函数定函数的结果原子更新当前值,返回上一个值
// count.get() 为 1:返回 1
System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));
// 返回 applyAsLong 得值
System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));
// 获取 atomicLong 的 value
System.out.println("get=" + count.get());
// 使用给定函数应用给当前值和给定值的结果原子更新当前值,返回上一个值
// 返回结果 1,上次结果
System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
// 返回结果 3 ,上次结果 1 + 2
System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
// 获取 atomicLong 的 value
System.out.println("get=" + count.get());
}
}
一串代码送给你,运行结果请参考:
getAndIncrement=0
incrementAndGet=2
incrementAndGet=2
incrementAndGet=0
addAndGet=10
getAndSet=10
get=100
*********** JDK 1.8 ***********
getAndUpdate=100
getAndUpdate=1
get=1
getAndAccumulate=1
getAndAccumulate=3
get=5
不安全并发计数
public class AtomicExample2 {
// 请求总数
public static int requestTotal = 1000;
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
long start = System.currentTimeMillis();
for (int i = 0; i < requestTotal; i++) {
new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
add();
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("count=" + count);
System.out.println("耗时:" + (System.currentTimeMillis() - start));
}
private static void add() {
++count;
}
}
懵懂少年是否对 CountDownLatch
有疑问吗?CountDownLatch
又称 倒计数器
, 也就是让一个线程或者多个线程等待其他线程结束后再继续自己的操作,类似加强版 join()
。
AtomicLong 实现并发计数
public class AtomicExample3 {
// 请求总数
public static int requestTotal = 5000;
public static AtomicLong count = new AtomicLong(0);
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
long start = System.currentTimeMillis();
for (int i = 0; i < requestTotal; i++) {
new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
add();
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("count=" + count.get());
System.out.println("耗时:" + (System.currentTimeMillis() - start));
count.addAndGet(200);
System.out.println("count=" + count.get());
}
private static void add() {
//count.incrementAndGet();
count.getAndIncrement();
}
}
走进源码
在 Jdk1.7 中,AtomicLong 的关键代码如下:
static {
try {
// 获取内存 value 内存中的地址
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 省略其他代码.....
public final long getAndIncrement() {
for(;;)
long current = get();
long next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
getAndIncrement()
进去乍一看,无限循环,这不就如一个痴情男孩一样,一直等待他的女神回信,不回信一直等啊等。
long current = get();
获取AtomicLong
中的 value 值。long next = current + 1;
: 在当前记录 + 1。compareAndSet(current, next)
: 通过 compareAndSet方法来进行原子更新操作,将当前的值跟内存中的值进行比较,相等,则内存中没有被修改,直接写入新的值到主内存中,并return true,否则直接return false。
在 Jdk1.8 中,AtomicLong 的关键代码如下:
/**
* 原子更新导致值
*
* @return 返回旧值
*/
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
//
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
var1
: 需要修改的类对象var2
:修改的字段的内存地址var6
是修改前字段的值,若是没其余线程修改即与var2
相等var6+var4
: 修改后字段的值,也就是新值compareAndSwapLong
:当字段实际值和var6值相当的时候,才会设置其为 var6+var4 。this.getLongVolatile(var1, var2)
:获取对象obj中偏移量为offset的变量对应 volatile 语义的值。
从上面的代码可以看出AtomicLong
在 jdk 7 的循环逻辑,在 JDK 8 中原子操作类 unsafe 内置了。之所以内置应该是考虑到这个函数在其他地方也会用到,而内置可以提高复用性。