突击并发编程 JUC 系列演示代码地址:
https://github.com/mtcarpenter/JavaTutorial
Java
从 JDK 1.5
开始提供了 java.util.concurrent.atomic
包(以下简称 Atomic
包),这个包中的原子操作类提供了一种用法简略、性能高效、线程平安地更新一个变量的形式。原子类通过 CAS (compare and swap)
、volatile
和 native
办法实现,比 synchronized
开销更小,执行效率更高,在多线程环境下,无锁的进行原子操作。
Atomic 包分类
对其进行分类如下:
为了防止一个章节内容过多,导致大家提前下车,会通过几个章节进行 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()
。
countDown
: 执行一次,计数器的数值 -1。await
: 期待计算器的值为 0,才进行前面的操作,就像一个栅栏一样。
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();}
}
走进源码
一段段小小的案例演示,曾经无奈满足糊涂少年了,那就加餐,加餐,上面分类介绍上面 JDk 1.7
和 JDK1.8
的底层实现。
在 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 内置了。之所以内置应该是思考到这个函数在其余中央也会用到,而内置能够进步复用性。
欢送关注公众号 山间木匠, 我是小春哥,从事 Java 后端开发,会一点前端、通过继续输入系列技术文章与文会友,如果本文能为您提供帮忙,欢送大家关注、点赞、分享反对,_咱们下期再见!_