原子类的CAS和ABA问题
多线程修改一个值,因为线程会从主内容拷贝副本到自己的工作空间所以会导致这个值出现脏数据,并发即保证修改值操作是一个原子操作
采用原子类 AtomicInteger,AtomicLong,AtomicBoolean, 原子类的底层操作是采用CAS操作,而CAS是CPU层面提供的原子操作,
即原子类能保证操作具备原子性(原理:CPU执行CAS操作时会使其他CPU核缓存行数据设置失效,致使同一时间不可能有两个核操作一个数据来保证顺序操作)
然而:
CAS存在ABA问题,对于数字计算ABA不影响结果,但是对于对象,字符串具备不同的意义,关于字符串处理ABA问题JAVA中有
AtomicStampedReference<T>,AtomicMarkableReference<T>来解决
AtomicStampedReference初始化设置对象和版本号,CAS时也加版本号处理,类似数据库的乐观锁
AtomicMarkableReference初始化设置对象和Boolean,CAS时可以判断是否被修改过
ReentrantLock,AtomicInteger,Semaphore并发情况实验
多线程操作数据时发生结果和预期不一致的问题
先来一波不安全操作代码:
public class Test implements Runnable{
private static int count = 1000;
@Override
public void run() {
count--;
System.out.println(count);
}
public static void main(String[] args) {
ThreadPoolExecutor threadPool =ThreadPoolExecutorUtil.getInstance();
for (int i = 0; i <1000 ; i++) {
threadPool.execute(new Test ());
}
threadPool.shutdown();
while (true){
if(threadPool.isTerminated()){
System.out.println("count="+count);
break;
}
}
}
}
最后发现 count不是每次都是=0,而且打印出来的count--打印值有相同的,问题在于count--并不是原子操作,涉及数据在线程工作空间到主内存空间的数据交互,导致出现脏数据
实验1: 我们将上诉代码count加上voliate修饰 private static int voliate count = 1000;
实验结果:和没加是一样的,count结果有时不符合预期,可见voliate不能保证数据操作原子性
实验2:我们将上诉代码count改为private static AtomicInteger count = new AtomicInteger(1000);
实验结果: count结果可预期一致,AtomicInteger可以保证操作原子性
实验3:我们加入reentrantLock
private static int count = 1000;
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
lock.lock();
count--;
System.out.println(count);
} finally {
lock.unlock();
}
}
实验结果: 与预期结果一致
实验4:加入Semaphore
private Semaphore semaphore = new Semaphore(10);
@Override
public void run() {
try {
semaphore.acquire();
count--;
System.out.println(count);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
实验结果: Semaphore控制并发数=1时是安全的,设置>1时会出现与预期不符数据
实验5: synchronized 同步方法不介绍了,count与预期一致
多线程下数据安全操作全依赖原子性:
1.单线程实现操作的原子性,线程顺序执行,缺点一个一个处理太慢
2.synchronized/lock 锁住的代码块相当于原子操作,优点:组合操作形成原子操作
3.使用原子类实现,只能对原子值的改变具有原子性
4.volatile只能保证变量线程间,假设需要一个flag在两个线程间使用,未加关键字的时一个线程改了值另一个线程可能不知
static flag =false,应该使用volatile修饰
注: CAS属于乐观锁状态,synchronized/lock属于独占锁悲观锁,通常CAS效率比悲观锁高