Summary: comparison between the security thread described herein, atomic, and the package under the category java.lang.Number CAS operation, the synchronized locks, and each of the atomic operation.
Thread safety
-
- Thread-safe?
- Thread safety?
- Atomicity
- Atomic package classes and CAS:
- AtomicInteger
- AtomicLong and LongAdder
- AtomicBoolean
- AtomicIntegerFieldUpdater
- AtomicStampReference the ABA problem with CAS
- AtomicLongArray
- synchronized
- a synchronized block of code modification
- modification of a synchronized method
- synchronized modification of a static method
- synchronized modification of a class
- Comparison among atomic operations
- Atomic package classes and CAS:
Thread-safe?
When multiple threads access a class, regardless of the runtime environment using what scheduling or how these processes will be performed alternately, and in the calling code does not require any additional synchronization or synergistic , this class can show the correct behavior , then call this class is thread-safe.
Thread safety?
Thread safety is mainly reflected in three aspects: atomicity, visibility, orderly
- Atomic: provides exclusive access , only one thread at a time to operate it
- Visibility: main memory modify a thread can be observed in time to other threads
- Ordering: a thread other threads observation instruction execution order, since there is an instruction reordering, the general observation disorderly.
The basis of the code: The following code is used to describe knowledge points below, all of the code are to be modified in this code basis.
1 |
public class CountExample { |
Atomicity
Speaking of atomicity, a total of two aspects need to learn about, is a JDK already provide good Atomic package, they are using the atomic operations CAS complete thread, and the other is to use the lock mechanism to handle between threads atomicity. Lock include: synchronized, Lock
Atomic package classes and CAS:
We AtomicInteger class from the simplest to understand what is CAS
AtomicInteger
The sample code is to ensure that the upper side of the thread by atomic AtomicInteger class.
So how does it guarantee the atomicity of it? We then analyze its source. Example, +1 to the count variable, uses incrementAndGet method, the source of this method calls a method named the unsafe.getAndAddInt
1 |
public final int incrementAndGet() { |
而getAndAddInt方法的具体实现为:
1 |
public final int getAndAddInt(Object var1, long var2, int var4) { |
在此方法中,方法参数为要操作的对象Object var1、期望底层当前的数值为var2、要修改的数值var4。定义的var5为真正从底层取出来的值。采用do..while循环的方式去获取底层数值并与期望值进行比较,比较成功才将值进行修改。而这个比较再进行修改的方法就是compareAndSwapInt就是我们所说的CAS,它是一系列的接口,比如下面罗列的几个接口。使用native修饰,是底层的方法。CAS取的是compareAndSwap三个单词的首字母.
另外,示例代码中的count可以理解为JMM中的工作内存,而这里的底层数值即为主内存,如果看过我上一篇文章的盆友就能把这一块的知识点串联起来了。
1 |
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); |
AtomicLong 与 LongAdder
LongAdder是java8为我们提供的新的类,跟AtomicLong有相同的效果。首先看一下代码实现:
1 |
AtomicLong: |
那么问题来了,为什么有了AtomicLong还要新增一个LongAdder呢?
原因是:CAS底层实现是在一个死循环中不断地尝试修改目标值,直到修改成功。如果竞争不激烈的时候,修改成功率很高,否则失败率很高。在失败的时候,这些重复的原子性操作会耗费性能。
知识点: 对于普通类型的long、double变量,JVM允许将64位的读操作或写操作拆成两个32位的操作。
LongAdder类的实现核心是将热点数据分离,比如说它可以将AtomicLong内部的内部核心数据value分离成一个数组,每个线程访问时,通过hash等算法映射到其中一个数字进行计数,而最终的计数结果则为这个数组的求和累加,其中热点数据value会被分离成多个单元的cell,每个cell独自维护内部的值。当前对象的实际值由所有的cell累计合成,这样热点就进行了有效地分离,并提高了并行度。这相当于将AtomicLong的单点的更新压力分担到各个节点上。在低并发的时候通过对base的直接更新,可以保障和AtomicLong的性能基本一致。而在高并发的时候通过分散提高了性能。
1 |
源码: |
缺点:如果在统计的时候,如果有并发更新,可能会有统计数据有误差。实际使用中在处理高并发计数的时候优先使用LongAdder,而不是AtomicLong在线程竞争很低的时候,使用AtomicLong会简单效率更高一些。比如序列号生成(准确性)
AtomicBoolean
这个类中值得一提的是它包含了一个名为compareAndSet的方法,这个方法可以做到的是控制一个boolean变量在一件事情执行之前为false,事情执行之后变为true。或者也可以理解为可以控制某一件事只让一个线程执行,并仅能执行一次。
他的源码如下:
1 |
public final boolean compareAndSet(boolean expect, boolean update) { |
举例说明:
1 |
//是否发生过 |
AtomicIntegerFieldUpdater
这个类的核心作用是要更新一个指定的类的某一个字段的值。并且这个字段一定要用volatile修饰同时还不能是static的。
举例说明:
1 |
@Slf4j |
AtomicStampReference与CAS的ABA问题
什么是ABA问题?
CAS操作的时候,其他线程将变量的值A改成了B,但是随后又改成了A,本线程在CAS方法中使用期望值A与当前变量进行比较的时候,发现变量的值未发生改变,于是CAS就将变量的值进行了交换操作。但是实际上变量的值已经被其他的变量改变过,这与设计思想是不符合的。所以就有了AtomicStampReference。
1 |
源码: |
AtomicStampReference的处理思想是,每次变量更新的时候,将变量的版本号+1,之前的ABA问题中,变量经过两次操作以后,变量的版本号就会由1变成3,也就是说只要线程对变量进行过操作,变量的版本号就会发生更改。从而解决了ABA问题。
解释一下上边的源码:
类中维护了一个volatile修饰的Pair类型变量current,Pair是一个私有的静态类,current可以理解为底层数值。
compareAndSet方法的参数部分分别为期望的引用、新的引用、期望的版本号、新的版本号。
return的逻辑为判断了期望的引用和版本号是否与底层的引用和版本号相符,并且排除了新的引用和新的版本号与底层的值相同的情况(即不需要修改)的情况(return代码部分3、4行)。条件成立,执行casPair方法,调用CAS操作。
AtomicLongArray
这个类实际上维护了一个Array数组,我们在对数值进行更新的时候,会多一个索引值让我们更新。
原子性,提供了互斥访问,同一时刻只能有一个线程来对它进行操作。那么在java里,保证同一时刻只有一个线程对它进行操作的,除了Atomic包之外,还有锁的机制。JDK提供锁主要分为两种:synchronized和Lock。接下来我们了解一下synchronized。
synchronized
依赖于JVM去实现锁,因此在这个关键字作用对象的作用范围内,都是同一时刻只能有一个线程对其进行操作的。
synchronized是java中的一个关键字,是一种同步锁。它可以修饰的对象主要有四种:
- 修饰代码块:大括号括起来的代码,作用于调用的对象
- 修饰方法:整个方法,作用于调用的对象
- ———————————————————————–
- 修饰静态方法:整个静态方法,作用于所有对象
- 修饰类:括号括起来的部分,作用于所有对象
synchronized 修饰一个代码块
被修饰的代码称为同步语句块,作用的范围是大括号括起来的部分。作用的对象是调用这段代码的对象。
验证:
1 |
public class SynchronizedExample { |
结果:不同对象之间的操作互不影响
synchronized 修饰一个方法
被修饰的方法称为同步方法,作用的范围是大括号括起来的部分,作用的对象是调用这段代码的对象。
验证:
1 |
public class SynchronizedExample |
结果:不同对象之间的操作互不影响
TIPS:
如果当前类是一个父类,子类调用父类的被synchronized修饰的方法,不会携带synchronized属性,因为synchronized不属于方法声明的一部分
synchronized 修饰一个静态方法
作用的范围是synchronized 大括号括起来的部分,作用的对象是这个类的所有对象。
验证:
1 |
public class SynchronizedExample{ |
结果:同一时间只有一个线程可以执行
synchronized 修饰一个类
验证:
1 |
public class SynchronizedExample{ |
Results: Only one thread can execute
Comparison among atomic operations
- synchronized: the lock can not be interrupted for less competitive, good readability
- Lock: interruptible lock, with manifold, to maintain normal when fierce competition
- Atomic: when fierce competition to maintain normal, better than the performance Lock, you can only synchronize one value
Reprinted with permission by the author, the original address: https://blog.csdn.net/jesonjoke/article/details/79837508