目录
原子性
Atomic包--CAS(重点)
CAS原理
CAS的ABA问题
LongAdder与AtomicLong
LongAdder
AtomicLong
AtomicLong和LongAdder区别(重点)
AtomicReference与AtomicIntegerFieldUpdater
AtomicReference
AtomicIntegerFieldUpdater
AtomicBoolean(可用于要求只执行一次的场景)
锁--synchronized
原子性对比
可见性
可见性-Synchronized
可见性-volatile
有序性
happens-before原则
原子性:提供了互斥访问,同一时刻只能有一个线程来对他进行操作
可见性:一个线程对主内存的修改可以及时的被其他线程观察到
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序
AtpmicXXX:CAS、 unsafe.compareAndSwapInt
@Slf4j
@ThreadSafe
public class ConcurrencyExample2 {
//请求总数
public static int clientTotal = 5000;
//同时并发执行的线程数
public static int threadTotal = 200;
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
ExecutorService exc = newCachedThreadPool();
final Semaphore semaphore =new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0;i < clientTotal;i++) {
exc.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
exc.shutdown();
log.info("count:{}",count.get());
}
private static void add(){
//count++;
//count.getAndIncrement();
count.incrementAndGet();
}
}
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); //java底层方法
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
var1:传入的对象 var2:当前的值 var5:点用户底层方法得到底层当前值,没有其他线程来处理count这个对象时,应等于var2.
compareAndSwapInt(var1, var2, var5, var5 + var4)
当前count的这个对象var1,如果当前的值var2和底层的值var5相等,则替换成var5+var4;
AtomicStampedReference类
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Paircurrent = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
在CAS操作的时候,其他线程将变量A的值改成了B,又改回了A,本线程使用期望值A与当前变量进行比较时发现A变量没有变,于是CAS 就将A值进行了交换操作,实际上该值已被其他线程改变过,这与设计思想不符合。
解决思路:
每次变量更新的时候,将变量版本号加1,只要该变量被其他线程更改过,版本号就会发生递增变化。
@Slf4j
@ThreadSafe
public class AtomicExample3 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static LongAdder count = new LongAdder();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
count.increment();
}
}
@Slf4j
@ThreadSafe
public class AtomicExample2 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static AtomicLong count = new AtomicLong(0);
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count.get());
}
private static void add() {
count.incrementAndGet();
// count.getAndIncrement();
}
}
扩展知识点:对普通类型的Long和double变量,jvm允许将64位的读操作或写操作拆成2个32位的操作
LongAddr核心
将热点数据分离,可以将AtomicLong内部核心数据value,分离成为一个数组,每个线程访问时,通过hash等算法映射到其中一个数字进行计数,而最终的计算结果为这个数组求和累加。热点数据value被分离成多个cell,每个cell独立维护内部值,当前实际值有每个cell累积合成。热点就被有效分离,提高了并行度。LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点上,在低并发的时候通过对base的直接更新,可以很好的保障和AtomicLong性能基本一致。而在高并发的时候提高分散提高性能。
LongAddr缺陷
在统计的时候如果有并发更新,可能会导致统计的数据有误差,实际使用中有高并发计数的时候,我们可以优先使用LongAddr,而不是继续使用AtomicLong,当然在线程竞争很低的情况下进行计数,使用AtomicLong还是更简单,更直接一些,并且效率会稍高一点。
@Slf4j
@ThreadSafe
public class AtomicExample4 {
private static AtomicReferencecount = new AtomicReference<>(0);
public static void main(String[] args) {
count.compareAndSet(0, 2); // 2
count.compareAndSet(0, 1); // no
count.compareAndSet(1, 3); // no
count.compareAndSet(2, 4); // 4
count.compareAndSet(3, 5); // no
log.info("count:{}", count.get());
}
}
@Slf4j
@ThreadSafe
public class AtomicExample5 {
private static AtomicIntegerFieldUpdaterupdater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count"); //字段被volatile修饰,非static
@Getter
public volatile int count = 100;
public static void main(String[] args) {
AtomicExample5 example5 = new AtomicExample5();
if (updater.compareAndSet(example5, 100, 120)) {
log.info("update success 1, {}", example5.getCount());
}
if (updater.compareAndSet(example5, 100, 120)) {
log.info("update success 2, {}", example5.getCount());
} else {
log.info("update failed, {}", example5.getCount());
}
}
}
@Slf4j
@ThreadSafe
public class AtomicExample6 {
private static AtomicBoolean isHappened = new AtomicBoolean(false);
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
test();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("isHappened:{}", isHappened.get());
}
//只会执行一次
private static void test() {
if (isHappened.compareAndSet(false, true)) { //可用于要求只执行一次的场景
log.info("execute");
}
}
}
synchronized:同步锁,依赖JVM实现锁,作用对象的作用范围内,都是同一时刻只有一个线程操作
修饰的地方
Lock:提供Lock接口类依赖于特殊的CPU指令,代码实现ReentrantLock
@Slf4j
public class SynchronizedExample1 {
// 修饰一个代码块
public void test1(int j) {
synchronized (this) {
for (int i = 0; i < 10; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
// 修饰一个方法
public synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test2(1);
});
executorService.execute(() -> {
example2.test2(2);
});
}
}
@Slf4j
public class SynchronizedExample2 {
// 修饰一个类
public static void test1(int j) {
synchronized (SynchronizedExample2.class) {
for (int i = 0; i < 10; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
// 修饰一个静态方法
public static synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
SynchronizedExample2 example1 = new SynchronizedExample2();
SynchronizedExample2 example2 = new SynchronizedExample2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test1(1);
});
executorService.execute(() -> {
example2.test1(2);
});
}
}
synchronized:不可中断锁,适合竞争不激烈,可读性好
Lock:可中断锁,多样化同步,竞争激烈时能维持常态
Atomic:竞争激烈时能维持常态,比Lock性能好;但是只能同步一个值
导致共享变量在线程间不可见的原因
JMM关于Synchronized的两条规定
通过加入内存屏障和禁止重排序优化来实现
换句话说:volate变量被线程访问时,读操作会强迫从主内存读取变量值,当写操作时,会强迫将修改值舒心到主内存。
public static volatile int count = 0;
。。。。。。
private static void add() {
count++;
// 1、count --取得当前值
// 2、+1 --加1
// 3、count -- 刷新到主存
} //假如两个进程同时得到当前值,但是他们同时进行了加1 操作,然后就漏掉了一次加1操作。
volatile 不适合累加场景,只能在有限的一些情形下使用 volatile 变量替代锁。但适合状态变量。还适合doublecheck场景.
volatile boolean inited = false;
//线程1
context = loadContext();
inited = true;
//线程2
while(!inited){
sleep();
}
doSomethingWithConfig(context)
要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使 x 的值在操作期间保持不变,而 volatile 变量无法实现这点。(然而,如果将值调整为只从单个线程写入,那么可以忽略第一个条件。)
大多数编程情形都会与这两个条件的其中之一冲突,使得 volatile 变量不能像 synchronized 那样普遍适用于实现线程安全。
Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序的过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
保证有序性方法
volatile 、snychronized、 Lock
如果两个执行操作不能通过h-b原则推导出来,则不能保证执行顺序,虚拟机会随意的对其进行重排序