并发安全的特性
定义: 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的
主要体现在下面三个方面:
1.原子性: 提供了互斥访问,同一时刻只能有一个线程对它进行操作。
2.可见性: 一个线程对主内存的修改可以及时被其它线程观察到。
3.有序性: 一个线程观察其它线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无章。
e
原子性:Atomic包 基于CAS操作
1、基本类型AtomicXXX
AtomicXXX : CAS、 java.util.concurrent(我们常说的JUC),该包提供了对Java中多种基本类型安全的操作方法。
其中用到了一个非常重要的方法,Unsafe.compareAndSwapXXX方法,就是我们常说的CAS操作了。
// 声明一个AtomicInteger 对象
static AtomicInteger count = new AtomicInteger(0);
//加一操作(单纯的进行加一操作是没有区别的)
count.incrementAndGet();//返回值是先加后获取
count.getAndIncrement();//返回值是先获取后加
这里看一下这两个方法的源码,在底层是如何实现的:
//返回值是先加后获取
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//返回值是先获取后加
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//var5是使用getIntVolatile()方法从底层获取当前对象的值
//compareAndSwapInt()方法,var2的值和var5的值是否相等,相等就执行相加的操作,如果不相等就把var5的值赋给var2,之后重新判断
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;
}
这两个方法是Java底层实现的,被native修饰
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public native int getIntVolatile(Object var1, long var2);
在JDK8中又新增了一个类LongAdder,相对于Atomic包中的AtomicLong有它自己的优点。我们知道在CAS的底层实现中,是在一个死循环中不断的去判断值与底层是否相等。
显然,在竞争不激烈的情况下修改成功的概率很高,但是如果竞争激烈的话,情况就不容乐观了,性能会受到一定的影响。而LongAdder可以在低并发的情况下与AtomicLong性能保持一致,在高并发的情况下,通过分散数据来得到较为客观的性能。
所以在低并发的情况下,优先使用AtomicLong,在高并发的情况下,优先使用LongAdder。并且,如果是在生成序列号这种全局唯一的,还是推荐使用AtomicLong来保证正确性。
二、数组类型AtomicXXXArray
主要有以下三种原子性数组:
1.AtomicLongArray: 提供对int[]数组元素的原子性更新操作。
2.AtomicIntegerArray: 提供对long[]数组元素的原子性更新操作。
3.AtomicReferenceArray: 提供对引用类型[]数组元素的原子性更新操作。
//这里主要介绍两个通用方法
//懒赋值,具有原子性但不具有可见性
atomicIntegerArray.lazySet(i,newValue);
//比较当前值和期望值是否相等,相等则更新为新的值,具有原子性和可见性
atomicIntegerArray.compareAndSet(i,expect,update);
三、引用类型
主要有以下三种原子性引用类:
1.AtomicReference: 引用类型原子类
2.AtomicStampedRerence: 原子更新引用类型里的字段原子类
3.AtomicMarkableReference : 原子更新带有标记位的引用类型
AtomicReference:
看下面这一段代码,可以猜一下最终的运行结果
private static AtomicReference<Integer> count = new AtomicReference<>(0);
public static void main(String[] args) throws Exception {
count.compareAndSet(0,2);
count.compareAndSet(0,1);//no
count.compareAndSet(1,3);//no
count.compareAndSet(2,4);
count.compareAndSet(3,1);//no
log.info("count:{}",count);
}
控制台打印结果:
count:5000
这就是AtomicReference类重要的一个方法,可以根据实际的值和期望的值是否相等来判断是否要进行更新操作。
三、对象的属性修改类型
1.AtomicIntegerFieldUpdater: 原子更新整形字段的更新器
2.AtomicLongFieldUpdater: 原子更新长整形字段的更新器
3.AtomicStampedReference : 原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
AtomicIntegerFieldUpdater:
private static AtomicIntegerFieldUpdater<AtomicExample5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class,"count");
//要求该字段必须被volatile修饰,且不能被static修饰
@Getter
public volatile int count = 100;
private static AtomicExample5 atomicExample5 = new AtomicExample5();
public static void main(String[] args) throws Exception {
if(updater.compareAndSet(atomicExample5,100,120)){
log.info("updater success, {}",atomicExample5.getCount());
}
if(updater.compareAndSet(atomicExample5,100,120)){
log.info("updater success, {}",atomicExample5.getCount());
}else{
log.info("updater failed, {}",atomicExample5.getCount());
}
}
控制台打印结果:
updater success, 120
updater failed, 120
整体上来说和AtomicReference类基本上差不多,该类可以实现根据类的某一个具体变量的值来控制更新操作,更加方便。但是,要求该字段必须被volatile修饰,且不能被static修饰。
AtomicStampedReference:CAS的ABA问题
**ABA问题:**指的是当一个线程X对变量进行操作的时候,另一个一个线程Y将变量值从A改成的B,又将B改回了A,这时线程X进行CAS操作是没有问题的,但是这已经违背了设计思想。
比如在这种情况下:
线程T1希望将栈顶替换为B,但是这时线程T2对栈进行了如下操作,将栈变成了A->C->D。
这时线程T1再去进行操作,因为栈顶元素还是A,所以会继续进行,此时栈顶元素B指向null,从而使得C和D被平白无故的丢失了。
所以为了解决这一问题,就推出了AtomicStampedRerence类。
/**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
这是这个类提供的compareAndSet方法,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题。
AtomicBoolean
希望某一段代码只执行一次,可以使用这个类来实现。
//请求总数
public static int clientTotal = 5000;
//同时并发执行的线程数
public static int threadTotal = 50;
private static AtomicBoolean isHappened = new AtomicBoolean(false);
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.info("Exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("isHappened:{}",isHappened.get());
}
private static void test(){
//如果希望的值是false则更新为true,并返回true
if(isHappened.compareAndSet(false,true)){
//要执行一次的代码
log.info("execute");
}
}
执行结果:
execute
isHappened:true