线程安全性–原子性—atomic
定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式,或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类时安全的
线程安全性,主要体现在三个方面,分别是:
原子性:提供了互斥访问,同一时刻只能有一个线程对它进行访问
可见性:一个线程对主内存的修改可以同时被其他线程访问到
有序性:一个线程观察到其它线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序
原子性就有必要提一下在jdk里面提供的atomic包,提供了很多atomic类
AtomicXXX:CAS、Unsafe.compareAndSwapInt
演示代码:
@ThreadSafe
public class CountExample2 {
//请求总数
private static int clientTotal=5000;
//同时并发请求的线程数
private static int threadTotal=200;
public static AtomicInteger count=new AtomicInteger(0);
// public static int count=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(new Runnable() {
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
add();
semaphore.release();
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
// System.out.println("count:"+count);
System.out.println("count:"+count.get());
}
private static void add() {
count.incrementAndGet();
}
//private static void add() {
// count++;
//}
}
两者区别:当需要返回值的时候两者会有区别,只是++操作的时候两者没有区别
IncrementAndGet:先做增加操作,在获取当前值
getAndIncrement:先获取当前值,在做增加操作
@ThreadSafe:线程安全
@NoThreadSafe:非线程安全的
为什么AtomicInteger是原子性,具体看看IncrementAndGet源码:
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
注:在IncrementAndGet源码里面,使用了一个叫unsafe的类,unsafe提供了一个叫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…语句,在do…while…语句里面核心调用了一个方法compareAndSwapInt,这个方法需要特别注意,源码如下:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
注:该方法使用native标注的方法,这个方法代表是java底层的方法,不是我们通过java来实现的,回过头来再来看看getAndAddInt方法的调用,第一个参数是Object,代表当前方法的对象,就是我们通过count传递过来的对象,第二个值是我们当前的值,比如我们要执行的是2+1=3这样的操作,第二个参数等于2,第三个参数等于1,这里的var5,等于调用底层getIntVolatile方法,而得到底层当前的值,如果没有别的线程访问的话,正常返回应该是2,那么应该传递到compareAndSwapInt这个方法的第一个参数应该是当前值应该是2,第二个参数是从底层传过来的2,那么var5 + var4就是从底层传过来的值加上1最后结果等于3,compareAndSwapInt这个方法需要达到的结果是。var1当前值,var2从底层传过来的值,var5 + var4最后是加上增加量1,就是最终的结果,总结就是如果当前对象和从底层传过来的值进行比较,如果相同的话,那么把它更新成var5 + var4这个值,当我们进来的时候第一个值是2,那么从底层传过来的值也应该是2,当我们进行更新成3的时候可能被其他线程更改,因此需要判断当前值和期望值是否相同,如果两个值相同的时候才被允许更新成最新值,否则继续while循环,继续判断,保证期望的值与底层的值完全相同时,才执行对应的加1的操作,把底层的值覆盖掉,compareAndSwapInt方法的核心就是我们所说的CAS的核心操作
AtomicLong、LongAdder
在JDK1.8中新增LongAdder和AtomicLong实现非常相似
public static LongAdder longAdder=new LongAdder();
LongAdder优缺点:在AtomicInteger中CAS底层实现是在一个死循环中不断尝试修改目标值,直到修改成功,如果竞争不激烈的时候修改成功的概率很高,某则的话修改失败的概率很高,在大量修改失败的情况下,CAS就会不断进行循环尝试,这对性能会有一定影响,对于普通对象的null,double对象,JVM允许将64位的读操作或写操作拆成两个32为的操作,那么LongAdder这个类,核心是将数据分离,比如atomiclong内部核心数据value分离成一个数组,哪个线程访问时通过hash等算法预测到其中一个数字进行计数,而最终的结果是这个数组的求和累加,其中热点数据value被分离成多个单元,每个单元独立维护各自的值,当前对象的实际值,由所有的单元累计合成,这样的话,数据被有效的分离,并提高的并行度,这样依赖LongAdder相当于在AtomicLong的基础上将单点的更新压力分散到各个节点上,在第并发的时候通过对base的直接更新可以很好的保障和atomic的性能基本一致,而在高并发的时候通过分散提高了性能,但是longadder也有自己的缺陷,在统计的时候如果有并发更新可能会导致统计的数据有误差,实际在使用时可以优先使用LongAdder而不是继续使用AtomicLong,当然在线程竞争很低的情况下进行计数,使用Atomic会更简单更直接一些,并且效率会更高一些,其他情况下,需要全局唯一的数据的时候,AtomicLong才是正确的选择
AtomicBoolean
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
在类里有一个compareAndSet,在实际应用中如果希望某件事情只执行一次,在执行之前它的标记可能为false,一旦执行时候标记可能为true,通过调用compareAndSet方法分别传入false,true,就可以保证对应我们需要控制的代码只执行一次,甚至可以理解为当前只有一个线程可以执行这段代码,如果我们在就行完之后再把这段代码标记为false,那么这段代码可以继续执行,达到的效果就是这段代码同一时间是有一个线程可以执行