当多个线程访问某个类时,不管运行时环境采用 何种调度方式 或者这些线程将如何交替执行,并且在主调代码中 不需要任何额外的同步或协同,这个类都能表现出 正确的行为,那么这个类就是线程安全的。
- 可见性:一个线程对主内存的修改可以及时的被其他线程观察到。
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令 重排序的存在,该观察结果一般杂乱无序。
- 原子性:提供了互斥访问 , 同一个时刻只能有一个线程对它进行操作
原子性 Atomic 包
代码演示
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 计数到 5000 , 代码运行结果 为 5000
*/
@Slf4j
public class CountExample2 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
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();
}
}
来看一下它的实现源码 , Atomic
在实现的时候, 使用了一个 unsafe
的类, unsafe
提供了一个 getAddAddInt
的方法 , 来看一下 这个方法的实现
incrementAndGet 方法实现
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
getAndAddInt 方法实现
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;
}
源码说明, 在这个源码实现里 , 用`do while
作为主体实现的 , 在 while
条件里 , 调用了一个核心的方法 compareAndSwapInt
在这个 getAndAddInt
方法里 , 传来的第一个参数是 请求的对象 , 就是上面示例代码里的 count
,第二个值是当前的值 ,比如当前执行的是 2 +1
这个操作, var2
就是 2
,第三个参数 var4
的值是 1
, 而 var5
是底层当前的值. 如果没有其他线程对对象 count
进行操作, 其返回的底层的值应该是 2
, 此时, 当前值 2
和底层获取到的值 2
是相等的, compareAndSwapInt
判断当前参数 var2
和底层var5
的值相等, 则执行相加操作 将 底层获取的值 var5
加上 被加数var4
,这个方法的最终目的就是, 对于这个传过来的 对象 count
如果底层的值和当前的值时相等的, 就将其更新为目标值 .
上面的代码的增加操作中, 在进行 2 + 1
操作的是时候, 对象 count
可能被其他线程更新, 当前值var2
就和 var5
不相等了, 所有就不能更新目标值 ,那么再次取出 底层的值 var5
, var2
这个值再重新从当前对象 count
取一次, 再次判断是否符合更新要求 . 就是通过这样不停的循环, 当 var2
与 var5
完全相同的时候, 才进行更新值 . 这个 compareAndSwapInt
的核心 就是所谓的 CAS
的核心.
Atomic
还有提供了 一个 类 AtomicLong
, 其实现和 AtomicInteger
一样.
在Java 8 里 , Atomic
提供了 一个 LongAdder
类 , 上面通过看 CAS
底层实现的时候知道了, 它是通过一个死循环,不断的长沙市修改目标值 , 直到修改成功 , 如果并发不是很好的情况下, 修改成功的几率很高 , 如果大量修改失败, 这些原子操作就会进行多次的循环尝试, 因此性能会受到一定的影响. 这里有一个额外的知识点, 对于 long double 类型的变量, jvm 允许将 64 位的读操作者 或者写操作 拆分成两个 32 位的操作 . LongAdder
这个类的设计 , 其核心是将热点数据分离, 比如它将 AtomicLong
内部的核心数据 value ,分离成一个数组 , 每个线程访问时候, 通过hash 等算法 ,将其映射到其中一个数字进行计数, 而最终的结果呢, 是这个数组的求和累加 . 热点数据value会被分离成多个的cell ,每个cell 独自维护内部的值, 当前对象的值由所有的cell 累计合成. 这样热点数据就进行了有效的分离 , 并提高了并行度 . 这样LongAdder
就相当于在 AtomicLong
基础上, 把单点的更新压力, 分散到各个节点上 .
LongAdder
推荐阅读博客 LongAdder