原文连接:(http://www.studyshare.cn/blog/details/1166/0 )
一、原子操作是什么?
如果有两个线程分别执行两个操作A和B,从第一个线程执行A操作的角度来看,另外一个线程执行B操作,要么将B操作执行完,要么B操作压根儿不执行,那么A操作和B操作对彼此来讲就是原子的。
二、原子操作如何实现?
1、使用锁:锁机制就是将一个操作执行完毕后,其他操作获取锁然后继续执行,通过对多个操作进行隔离,从而实现原子操作。
2、CAS:CAS是Compare And Swap的简称,即:比较并交换。这是当前的处理器基本都支持的一种指令。每个CAS指令包括三个运算符,一个内存地址V,一个期望值A和一个新值B,CAS指令执行的时候是去判断这个地址V上的值和期望值A是否相等,相等则将地址V上的值修改为新值B,不等则不作任何操作。CAS操作实际实现是在一个循环中不断执行CAS指令,直到成功为止。
更多深度技术文章,在这里。
java开发工具下载地址及安装教程大全,点这里。
三、为什么要使用原子操作?
1、我们知道原子操作可以通过锁来实现,但锁是一种粗粒度的操作,使用锁会存在一些问题,例如:(1)当一个线程占用了锁资源,其他线程就被阻塞,然而被阻塞的线程的优先级又很高。此时也只能等待锁释放。如果获得锁的线程一致不释放的话那情况就更糟糕了。(2)如果有大量的线程来竞争资源,那么CPU就需要花费大量的时间和资源来处理这些竞争。这对CPU这种稀缺资源是不利的。
2、如果我们要实现一个多线程下的程序计数器。使用锁机制无疑是笨重的。那么CAS就能很好的解决这种需求。
3、JDK的源码中非常多的类中都使用了CAS操作,了解并清楚CAS操作对阅读JDK源码是必要的。
四、CAS如何实现线程安全?
CAS实现线程安全不在代码层面处理,而是交给硬件-CPU和内存,利用CPU的多处理能力,实现硬件层面的阻塞,然后加上volidate变量的特性即可实现基于原子操作的线程安全。
五、CAS实现原子操作的三大问题
1、ABA问题:因为CAS在操作值的时候先要检查值有没有发生变化,如果没有发生变化则更新,那么所谓ABA问题就是当原来的值是A,改变成B,然后又改变成A,那么CAS检查的时候发现值还是初始的A,并没有发生变化。然后实际上却是已经变化了。如何解决ABA问题:加版本号,即在A变成B再变成A的时候,每一步操作加一个version,即:1A->2B->3A,这样就可以根据版本号是否有变化即可判断是否发生了ABA问题。
2、循环时间长、开销大:上面说过是在一个循环中不断执行CAS指令(自旋CAS),如果长时间都不成功的话,则会对CPU带来很大的执行开销。
3、只能保证一个共享变量的原子操作:即一个CAS操作只能保证一个共享变量的的原子性。如果有多个共享变量则不能使用CAS操作,可使用锁。也可以将多个共享变量合并然后使用CAS操作。java1.5之后提供了AtomicReference类来保证引用对象之间的原子性。可以将多个变量放进一个对象中然后进行CAS操作。
六、JDK中常见原子操作类的使用
1、AtomicInteger
•int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
•boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
•int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。
•int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
2、AtomicIntegerArray
主要是提供原子的方式更新数组里的整型,其常用方法如下。
•int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。
•boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。
需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。
3、AtomicReference
原子更新引用类型,更多具体使用可查看jdk文档
更多原子操作类,在jdk文档java.util.concurrent.atomic包下。
原创文章,转载请注明出处。
java开发工具下载地址及安装教程大全,点这里。
更多深度技术文章,在这里。