线程安全性
> 当多线程访问某个类时,不管运行环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何的同步或者协同,这个类都能表现出正确的行为,那么这个类就是线程安全的.
原子性
> 提供互斥访问,同一时刻只有一个线程对它进行访问.
Atomic包
> 位于java.util.concurrent.atomic
,AtomicXXX : CAS、Unsafe.compareAndSwapXXX
> CAS(Compare and swap)
比较和替换是设计并发算法用的的一项技术,比较和替换是用一个期望值和一个变量的当前值进行比较,如果变量的值和期望值相等,那么就用一个新值替换变量的值.
案例
> 线程安全
package com.keytech.task;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
/**
* @className: AtomicTest
* @description: TODO 类描述
* @author: mac
* @date: 2020/12/27
**/
public class AtomicTest {
private static Integer clientTotal=5000;
private static Integer threadTotal=200;
private static AtomicInteger count=new AtomicInteger(0);
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
Semaphore semaphore= new Semaphore(threadTotal);
for (int i = 0; i <clienttotal ; i++) { executorservice.execute(()->{
try{
semaphore.acquire();
update();
semaphore.release();
}catch (Exception e){
e.printStackTrace();
}
});
}
executorService.shutdown();
System.out.println("count:"+count);
}
private static void update(){
count.incrementAndGet();
}
}
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;
}
> compareAndSwapInt(this, stateOffset, expect, update)
这个方法的作用就是通过cas技术来预测stateOffset变量的初始值是否是expect,如果是,那么就把stateOffset变量的值变成update,如果不是,那么就一直自旋转,一直到stateOffset变量的初始值是expect,然后在在修改stateOffset变量的值变成update
LongAddr
>线程安全
package com.keytech.task;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.LongAdder;
/**
* @className: LongAddrTest
* @description: TODO 类描述
* @author: mac
* @date: 2020/12/28
**/
public class LongAddrTest {
private static Integer clientTotal=5000;
private static Integer threadTotal=200;
private static LongAdder count=new LongAdder();
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
Semaphore semaphore=new Semaphore(threadTotal);
for (int i = 0; i < clientTotal; i++) {
try{
semaphore.acquire();
update();
semaphore.release();
}catch (Exception e){
e.printStackTrace();
}
}
executorService.shutdown();
System.out.println("count"+count);
}
private static void update(){
count.increment();
}
}
AtomicLong
> 线程安全
package com.keytech.task;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
/**
* @className: AtomicLongTest
* @description: TODO 类描述
* @author: mac
* @date: 2020/12/28
**/
public class AtomicLongTest {
private static Integer clientTotal=5000;
private static Integer threadTotal=200;
private static AtomicLong count=new AtomicLong();
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
Semaphore semaphore=new Semaphore(threadTotal);
for (int i = 0; i < clientTotal; i++) {
try{
semaphore.tryAcquire();
update();
semaphore.release();
}catch (Exception e){
e.printStackTrace();
}
}
executorService.shutdown();
System.out.println("count"+count);
}
private static void update(){
count.incrementAndGet();
}
}
LongAddr与AtomicLong的区别
> AtomicLong的原理是依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。如果竞争不激烈,修改成功几率很高,否则失败概率很高,在失败几率很高的情况下,这些原子操作就会进行多次的循环操作尝试,因此性能会受到影响。
> 对于普通类型的Long和Doubble变量,JVM允许将64位的读操作或写操作拆成两个三十二位的操作。
> LongAdder则是内部维护一个Cells数组,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后当获取当前值时候是把所有变量的值累加后再加上base的值返回的。
> LongAdder的核心是将热点数据分离,比如说它可以将AtomicLong内部核心数据value分离成一个数组,每个线程访问时,通过hash等算法,映射到其中一个数字进行计数,最终的计数结果则会这个数据的求和累加,其中热点数据value会被分离成多个cell,每个cell独自维护内部的值,当前对象实际值为所有cell累计合成,这样的话,热点就进行了有效的分离,并提高了并行度。
> LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。
LongAddr与AtomicLong的使用场景
> 实际使用中,在处理高并发时,可以优先使用LongAdder,而不是继续使用AtomicLong,当然,在线程竞争很低的情况下,使用AtomicLong更简单更实际一些,并且效率会高些。其他情况下,比如序列号生成,这种情况下需要准确的数值,全局唯一的AtomicLong才是正确的选择,而不是LongAdder
</clienttotal>
来源:oschina
链接:https://my.oschina.net/u/4232146/blog/4865809