什么是原子性呢?
原子性是指某个操作或者一些操作要么都成功,要么都失败,不允许出现因中断而导致的部分成功或部分失败的情况。
那 i++ 是线程安全的吗?
今天我们就从这个问题出发来讲解JAVA的原子操作。
当我们学习JAVA基础的时候就知道了i++操作,那你知道他是线程安全的吗?
在你心中肯定有那么一个人,嗯……不对,是有一个答案!
我们来做一个实验:
@Slf4j
public class Add {
public static void main(String[] args) {
AddThread addThread = new AddThread();
//创建10个线程
IntStream.range(0, 10).forEach(
value -> new Thread(addThread).start()
);
}
static class AddThread implements Runnable {
//初始化i值
private int i = 0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自增操作
i++;
log.info("当前线程:{} ,i值为:{}", Thread.currentThread().getName(), i);
}
}
}
执行结果如下:
[Thread-5] INFO com.xiaozhi.simple.Add - 当前线程:Thread-5 ,i值为:4
[Thread-1] INFO com.xiaozhi.simple.Add - 当前线程:Thread-1 ,i值为:3
[Thread-9] INFO com.xiaozhi.simple.Add - 当前线程:Thread-9 ,i值为:8
[Thread-4] INFO com.xiaozhi.simple.Add - 当前线程:Thread-4 ,i值为:2
[Thread-6] INFO com.xiaozhi.simple.Add - 当前线程:Thread-6 ,i值为:5
[Thread-0] INFO com.xiaozhi.simple.Add - 当前线程:Thread-0 ,i值为:2
[Thread-2] INFO com.xiaozhi.simple.Add - 当前线程:Thread-2 ,i值为:2
[Thread-8] INFO com.xiaozhi.simple.Add - 当前线程:Thread-8 ,i值为:7
[Thread-7] INFO com.xiaozhi.simple.Add - 当前线程:Thread-7 ,i值为:6
[Thread-3] INFO com.xiaozhi.simple.Add - 当前线程:Thread-3 ,i值为:3
可以看出部分线程的i值是相同的(例如:Thread-1、Thread-3),则说明出现了线程不安全的情况,那如何解决呢?
相信大家肯定都有自己的解决方案,这里就不贴出详细代码了,代码已经上传码云,文末有链接。
- 核心代码使用synchronized修饰
- 核心代码使用显示锁Lock包裹
- 使用JDK的原子类(AtomicInteger)
synchronized、lock、AtomicInteger性能如何呢?
那我们使用上一篇文章所介绍的JMH测量工具进行性能测试 测试代码如下:
@BenchmarkMode(Mode.AverageTime) //统计平均时间
@OutputTimeUnit(TimeUnit.MICROSECONDS) //单位微秒
@Warmup(iterations = 10) //预热10次
@Measurement(iterations = 10) //度量10次
public class SynchronizedVsLockVsAtomicInteger {
@State(Scope.Group)
public static class SynMonitor {
private int i = 0;
public void synInc() {
synchronized (this) {
i++;
}
}
}
@State(Scope.Group)
public static class LockMonitor {
private final Lock lock = new ReentrantLock();
private int i = 0;
public void lockInc() {
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
}
@State(Scope.Group)
public static class AtomicIntegerMonitor {
private AtomicInteger i = new AtomicInteger(0);
public void inc() {
i.incrementAndGet();
}
}
//定义基准方法,测试synchronized修饰的性能
@GroupThreads(10) //线程数量
@Group("sync") //线程组名称
@Benchmark
public void synInc(SynMonitor synMonitor) {
synMonitor.synInc();
}
//定义基准方法,测试显示锁Lock性能
@GroupThreads(10)
@Group("lock")
@Benchmark
public void lockInc(LockMonitor lockMonitor) {
lockMonitor.lockInc();
}
//定义基准方法,测试AtomicInteger性能
@GroupThreads(10)
@Group("atomic")
@Benchmark
public void atomicInc(AtomicIntegerMonitor atomicIntegerMonitor) {
atomicIntegerMonitor.inc();
}
public static void main(String[] args) throws Exception {
Options options = new OptionsBuilder().include(SynchronizedVsLockVsAtomicInteger.class.getSimpleName())
.addProfiler(StackProfiler.class)
.forks(1)
.timeout(TimeValue.seconds(10))
.build();
new Runner(options).run();
}
}
基准测试执行结果如下:
Benchmark Mode Cnt Score Error Units
SynchronizedVsLockVsAtomicInteger.atomic avgt 10 0.150 ± 0.007 us/op
SynchronizedVsLockVsAtomicInteger.lock avgt 10 0.247 ± 0.005 us/op
SynchronizedVsLockVsAtomicInteger.sync avgt 10 1.128 ± 0.408 us/op
不难看出其性能顺序为:AtomicInteger > 显示锁Lock > synchronized关键字
仔细的小伙伴肯定注意到了代码中我们配置了StackProfiler.class,那我们来看看线程状态数据:
Secondary result "com.xiaozhi.simple.SynchronizedVsLockVsAtomicInteger.atomic:·stack":
98.8% RUNNABLE
1.2% WAITING
Secondary result "com.xiaozhi.simple.SynchronizedVsLockVsAtomicInteger.lock:·stack":
78.2% WAITING
21.8% RUNNABLE
Secondary result "com.xiaozhi.simple.SynchronizedVsLockVsAtomicInteger.sync:·stack":
79.9% BLOCKED
19.3% RUNNABLE
0.8% WAITING
观察上面线程状态统计数据得知:
AtomicInteger线程的RUNNABLE状态高达了98.8%,并没有BLOCKED状态;而synchronized的BLOCKED状态高达了79.9%;
所以AtomicInteger的性能为什么如此之高? 是不是一下就明白了!
Atomic原子类
以上讲解那么多,也是为了展现出今天主角儿的强大之处。
-
那你知道JDK的原子类型有哪些吗?
我列举几个大家可能在开发过程中会用到的:
- AtomicInteger
- AtomicLong
- AtomicBoolean
- AtomicReference
- AtomicStampedReference
- AtomicArray
- AtomicFieldUpdater
-
这么多原子类型,分别怎么使用、用于什么场景呢?
今天我们先对AtomicInteger,AtomicLong进行详细讲解
AtomicInteger
与int的引用类型Integer类似,也是继承了Number类;但除此之外AtomicInteger还提供了很多原子性的操作方法。
AtomicInteger的内部有一个被volatile关键字修饰的成员变量value,实际上,AtomicInteger所提供的方法主要都是针对该变量value进行的操作。
以下是从源码摘抄出来的:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
//volatile修饰我们的值
private volatile int value;
}
-
创建AtomicInteger
1、使用无参构造进行创建 AtomicInteger atomicInteger = new AtomicInteger(); 2、使用有参构造进行创建 AtomicInteger atomicInteger = new AtomicInteger(1234567);其实无参构造等价于有参构造传入一个0值
-
Incremental操作
i ++或者i = i + 1 这样的操作是非原子性的,所以我们可以借助AtomicInteger中提供的原子性Incremental的操作方法
int getAndIncrement()
返回当前的value值,并对其进行自增操作
@Test public void getAndIncrement() { final AtomicInteger ai = new AtomicInteger(18); assert 18 == ai.getAndIncrement(); assert 19 == ai.get(); }int incrementAndGet()
直接返回自增后的值
@Test public void incrementAndGet() { final AtomicInteger ai = new AtomicInteger(18); assert 19 == ai.incrementAndGet(); assert 19 == ai.get(); } -
Decremental操作
i --或者i = i - 1 这样的操作也非原子性的,所以我们可以借助AtomicInteger中提供的原子性Decremental的操作方法
int getAndDecrement()
返回当前的value值,并对其进行自减操作
@Test public void getAndDecrement() { final AtomicInteger ai = new AtomicInteger(18); assert 18 == ai.getAndDecrement(); assert 17 == ai.get(); }int decrementAndGet()
直接返回自减后的值
@Test public void decrementAndGet() { final AtomicInteger ai = new AtomicInteger(18); assert 17 == ai.decrementAndGet(); assert 17 == ai.get(); } -
原子性的更新value值
boolean compareAndSet(int expect, int update)
expect代表当前AtomicInteger值,update代表需要设置的值,该方法会返回一个boolean结果
当expect值和当前值不一致时,会修改失败,返回结果为false
注意:boolean weekCompareAndSet(int expect, int update)与当前方法完全一样
@Test public void compareAndSet() { final AtomicInteger ai = new AtomicInteger(18); //传入值与ai值不一致 assert !ai.compareAndSet(123, 24); assert 18 == ai.get(); //传入值与ai值一致 assert ai.compareAndSet(18, 24); assert 24 == ai.get(); }int getAndAdd(int delta)
直接返回当前值,然后让value与delta进行相加操作
该方法实际上是基于自旋+CAS算法实现的原子性操作
@Test public void getAndAdd() { final AtomicInteger ai = new AtomicInteger(18); assert 18 == ai.getAndAdd(13); assert 31 == ai.get(); }int addAndGet(int delta);
将value值与delta值进行相加操作,并直接返回
@Test public void addAndGet() { final AtomicInteger ai = new AtomicInteger(18); assert 31 == ai.addAndGet(13); assert 31 == ai.get(); } -
AtomicInteger与函数式接口
函数式接口是JDK1.8引入的,AtomicInteger也提供了对函数式接口的支持
int getAndUpdate(IntUnaryOperator updateFunction)
int updateAndGet(IntUnaryOperator updateFunction)
@Test public void getAndUpdate02() { final AtomicInteger ai = new AtomicInteger(18); //传入lambda表达式 assert 18 == ai.getAndUpdate(i -> i * 3); assert 54 == ai.get(); } @Test public void updateAndGet02() { final AtomicInteger ai = new AtomicInteger(18); //传入lambda表达式 assert 54 == ai.updateAndGet(i -> i * 3); assert 54 == ai.get(); }int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction)
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction)
@Test public void getAndAccumulate() { final AtomicInteger ai = new AtomicInteger(18); //使用lambda表达式 assert 18 == ai.getAndAccumulate(14, Integer::sum); assert 32 == ai.get(); } @Test public void accumulateAndGet() { final AtomicInteger ai = new AtomicInteger(18); assert 35 == ai.accumulateAndGet(17, Integer::sum); assert 35 == ai.get(); }当AtomicInteger支持函数式接口后,我们应该感到无比的开心,因为可以自己DIY方法,且以原子性的方式进行执行
自定义函数式接口如下:
@Test public void getAndAccumulate02() { final AtomicInteger ai = new AtomicInteger(18); assert 18 == ai.getAndAccumulate(14, new IntBinaryOperator() { @Override public int applyAsInt(int left, int right) { assert 18 == left; assert 14 == right; //两值相加 int temp = left + right; //结果扩大7倍进行返回 return temp * 7; } }); //获取自定义函数式操作后的值 assert 224 == ai.get(); } -
AtomicInteger还有两个方法
void set(int value)
void lazySet(int value)
这两个方法达到的效果是一样的,直接修改AtomicInteger的value值
set方法修改被volatile修饰的value值会被强制刷新到主内存中,从而其他线程就可以立即看见,其原理还是volatile关键字底层的内存屏障。
内存屏障虽然足够轻量,但毕竟还是会带来性能开销。
在单线程下进行set操作时,就没必要保留这种内存屏障机制了,所以lazySet方法由此诞生!
JVM开发者对性能的追求真是达到了极致啊! 在此佩服、佩服!
针对这两个方法的性能测试详情结果,见文末码云代码链接。
AtomicLong
由于AtomicLong和AtomicInteger方法基本一致,我就不再文章中重复的列出讲解。
自上一篇文章JMH发出之后,有朋友说我太长了!
我当时有点蒙,结果是说我文章内容有点长;哎,没办法,为了详细讲解知识,怎么可以走马观花的草草了事呢?
但是太长了你们会受不了,所以这里还是适当精简一下。
内幕
经过上面漫长的过程,我们学习了AtomicInteger的使用。
那现在我们就来大概看看AtomicInteger的内幕吧!
在刚介绍AtomicInteger的时候,我贴出了一小段源码,我们再来看看:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
Unsafe是由C++编写的,内部存在很多大量的汇编CPU指令代码
valueOffset用于存放value的内存地址偏移量
我们来看看compareAndSet方法的源码 ——CAS
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
其中**unsafe.compareAndSwapInt(this, valueOffset, expect, update);**是一个native方法,能保证我们的操作是原子性的,会将传入的值与当前值进行比较,当expect值和当前值不一致时,会修改失败;
addAndGet等方法底层也是这个native操作,但是不会给我们返回失败操作的情况,其底层使用了do……while进行自旋的结果。
AtomicLong底层也有unsafe.compareAndSwapIntLong方法
由于native方法相对底层,反编译比较麻烦,我的知识能力有限,就不继续深入了!
今天对AtomicInteger、AtomicLong学习就到这里,相信大家对这个知识都比较清楚了吧;其他原子类我会在后面的文章中陆续更新。
码云代码链接如下:
感谢阅读。
祝大家工作愉快、身体健康!