一、原子性
概念:不可分割的
原子性的一个例子:a=5;—-赋值,只涉及到写入工作内存中
简单的理解:只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作
i++的问题
分析:i++在计算机的底层完成的步骤:读、改、写
过程:int temp=i;读———-→(2)i=i+1;改———-→(3)i=temp;写入到主存中
例1
package www.wzj.juc;
/*
* 一、i++ 的原子性问题:i++ 的操作实际上分为三个步骤“读-改-写”
* int i = 10;
* i = i++; //10
*
* int temp = i;
* i = i + 1;
* i = temp;
*
* 二、原子变量:在 java.util.concurrent.atomic 包下提供了一些原子变量。
* 1. volatile 保证内存可见性
* 2. CAS(Compare-And-Swap) 算法保证数据变量的原子性
* CAS 算法是硬件对于并发操作的支持
* CAS 包含了三个操作数:
* ①内存值 V
* ②预估值 A
* ③更新值 B
* 当且仅当 V == A 时, V = B; 否则,不会执行任何操作。
*/
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
//十个线程同时运行
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
private int serialNumber = 0;
// private volatile int serialNumber = 0;//说明
// private AtomicInteger serialNumber = new AtomicInteger(0);
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(getSerialNumber());
}
public int getSerialNumber(){
return serialNumber++;// 0、1、2、2、3、4、5、6、7、8、9
// return serialNumber.compareAndSet();//
}
}
分析过程:共享数据在内存中的操作过程。主存是”serialNumber=0″,线程1先把主存中的数据读取到缓存中,也即”int emp=serialNumber”;然后完成serialNumber=serialNumber+1;此时第三步:准备写入的时候(i=temp),准备把缓存中的数据写入到内存中,但是线程2抢占了CPU的执行权,也从主存中去读取数据,把”serialNumber=0″的值从主存中读过来(线程1还没有写入),此时线程2也完成上面的工作,先读过来”int temp=serialNumber”再运算”serialNumber=serialNumber+1″,但是线程1又抢到了,完成了写入内存的操作,然后打印,再接着线程2也写入主存中,也打印了,会发现二者的数据一样
思考:好像valiate关键字都能解决上述的问题,真的吗?
分析:如果volatile关键字修饰变量,内存具有可见性.上一篇我们谈到,volatile修饰的变量可以理解为在主存中的修改,此时
线程1:”int temp=serialNumber”以及”serialNumber=serialNumber+1″是在主存中完成的
线程2:”int temp=serialNumber”以及”serialNumber=serialNumber+1″也是在主存中完成的
问题来了:如果volatile修饰,仍然要完成仍然要完成三步操作,由于volatile不具有互斥性,多个线程可以对其进行更改。如果
仅用valiate修饰,无非是意味着当线程1准备写1的时候,线程2也读到了,也准备写1,这个问题依然存在。
总结:volatile只能保证内存可见性问题,不能保证原子性问题
再次分析:按照我们的需求”serialNumber++”明明是不可分割的,但是现在分割开来了
解决:原子变量
jdk1.5以后,提供了java.util.concurrent.atomic
常见的原子类:AtomicBoolean、AtomicInteger、AtomicLong、AtomicIntegerByteArray
特点:类里面封装的变量都是voliate修饰,保证内存可见性,那么如何保证原子性呢?利用CAS算法保证数据的原子性
二、CAS算法
概述:是硬件(底层)对于并发操作共享数据的支持,JVM对其也支持。
原理:包含三个操作,内存值 V、预估值 A、更新值 B,当且仅当 V==A时,V=B,否则将不做任何操作
以上面的例子为例,具体的判断过程:
(1)线程1首先会从主存中读取数据 V=0;
(2)在替换的时候(往主存中写入数据的时候)会去”读取”原来的旧值;(看是否有其它线程,趁其不注意修改了主存中的共享变量)
(3)如果读取到的旧值:A=0(表明没有其他的线程对其进行修改)
(4)此时进行运算,会把最终的值写入到主存中
理解为两步
(1)读取内存中是第一步
(2)比较和替换(写入)是第二步(此步同步的,一次只有一个线程能进来)
CAS算法如何处理例1出现的问题:线程2抢到了CPU执行权先”读取”主存中也是1(线程1还没来得及写入),然后线程1完成了写
入,然后线程2从内存中读取原来的旧值(其实已经发生变化)–预估值,发现serialNumber=2,发现与原来读取的数据不一样,
而要改的值也是2,此时什么也不做。
保证了:多个线程并发的要对主存中的数据修改的时候,有且只有一个线程会成功
CAS比原来的synchronized效率高的原因
原因:是此次不成功,但是不会处于阻塞的状态,即不会放弃CPU的执行权,可以立即尝试再去更新,所以CAS算法要比同步锁
的效率高
缺点:自己要写的步骤比较多(比如说一旦失败了怎么做)
随后:模拟CAS算法
AtomicInteger类中
常用的方法
(1)getAndIncrement—先获取,再递增(减略)–i++
(2)getAndSet———–先获取,再设置
(3)IncrementAndGet—先递增再获取—++i
(4)compareAndSet —-先比较再替换(例子中获取的)
相关资料:
点击打开链接