原子操作
原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。通俗讲就是你要么全部做完,要么一点也不做。
举例:
启动两个线程,每个线程中让静态变量count循环累加100次。那么其结果很可能会出现错误。
而给累加操作加上sychronized代码块,就能够得到正确的结果。这就叫实现了原子性操作,从而实现了线程安全。
那么这样做我们就安心了吗?有没有发现,sychronized用在这里反复加锁释放锁消耗性能,为了这个count++的操作有点大炮打蚊子了。这里用sychronized来实现原子性并不是高效的,那么就可以使用原子操作类来轻量级解决该问题。
原子操作类:
指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。
将count用AtomicInteger来声明,表明是原子操作类。最终的输出结果同样可以保证是200。Atomic类实现的底层正式使用了CAS机制。
CAS(Compare And Swap) 比较并替换
利用现代处理器都支持CAS的指令,循环这个指令,直到成功为止。
从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,总有别的线程要来抢占锁,所以每个线程都会先下手为强,先抢占到锁去执行操作。
而CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,先让自己执行操作,再去试图修改资源。
CAS机制当中使用了3个基本操作数:内存地址V,旧的值A,要修改的新值B。
更新一个变量的时候,只有当变量的值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
CAS过程演示图1.在内存地址V当中,存储着值为1的变量。
2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=1,要修改的新值B=2。
3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了2。
4.线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A为1不等于V的实际值2,提交失败。
5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=2,B=3。这个重新尝试的过程被称为自旋,其实是一个for (;;) {}代码死循环。
6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A为2和地址V2的实际值是相等的。
7.线程1进行SWAP,把地址V的值替换为B,也就是3。
我们如何使用原子类:
比如AtomicInteger类的使用:
public class AtomicUse {
static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
atomicInteger.getAndIncrement(); //表示i++
atomicInteger.getAndAdd(1); //表示i++,这里变化delta
atomicInteger.incrementAndGet(); //表示++i
atomicInteger.addAndGet(1); //表示++i, 这里变化 delta
}
}
CAS这么小巧还能达到原子性,线程安全,那么为什么不都用CAS呢,因为CAS也是有局限性的。
CAS的缺点:
1.CPU开销问题
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,在长时间自旋下,会给CPU带来很大的压力。
2.不能保证多个变量的原子性
CAS机制所保证的只是一个变量的原子性操作,因为只能进行一个地址上的值比较,而不能保证整个代码块的原子性。如果需要保证多个变量共同进行原子性的更新,就不得不使用Synchronized了。
解决办法:使用AtomicRefrence来引用对象,将多个需要保证原子性的变量存入对象中,采用修改对象值,直接更新对象对对象实现原子性达到对多个变量实现原子性。
3.ABA问题
这是CAS机制最大的问题所在。线程1要将变量值从A修改为了B,如果V地址上值是A,则修改为B。在这个期间,而又来一个线程2,将V地址上的值由A变为了C,又重新改为了A。现在线程1来看这个值仍然是A,没有发生变化,线程1就会将值改为B。这就是ABA问题。
ABA问题解决方案:
加入版本戳:要求每个线程改这个值时,同时要修改版本戳。 Java提供了AtomicMarkableReference和AtomicStampedReference两个类来解决ABA问题。AtomicMarkableReference只关心变量是否被改过,而AtomicStampedReference还能观察到被改变了几次。
网友评论