今天看了《Java并发编程的艺术》的第二章,最后介绍了原子操作的实现原理,受益匪浅,特在此进行总结。
原子(atomic)本意是“不能被进一步分割的最小粒子”,原子操作意为“不可被中断的一个或一系列操作”。
处理器实现原子操作方式:
(1) 总线锁:处理器提供一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,该处理器就可以独占共享内存。
书中用i++操作(读改写操作)来解释,如果i=1,进行两次i++操作,期望结果是3,但有可能结果是2,原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行加1操作,然后分别写入系统内存中。这样两次i++操作结果是2。
(2) 缓存锁:通过缓存锁定来保证原子性。
缓存锁定:内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言LOCK#信号(总线锁),而是修改内部的内存地址,并允许他的缓存一致性机制来保证操作的原子性。
缓存一致性:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是否过期,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存中。
两种情况不能使用缓存锁定:
1.当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行(cache line)时,处理器会使用总线锁定。
2.有些处理器不支持缓存锁定。
JAVA中实现原子操作:锁和循环CAS
锁:偏向锁、轻量级锁和互斥锁等。
循环CAS:使用处理器的CMPXCHG指令实现,自旋CAS的思路就是循环执行CAS操作直达成功为止。
1. ABA问题:值原来是A,变成了B,再变成A,那么CAS检查值时没有发生变化,但实际发生了。ABA问题解决思路就是加版本号AtomicStampedReference,这个类的compareAndSet方法的作用是先检查当前引用是否是预期引用,并检查当前标志是否是预期标志。全部相等才会做更新操作。
2.循环时间长开销大。
3.只能保证一个共享变量的原子操作:可以使用共享变量,AtomicReference类可保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
以下代码说明了多处理器i++(读改写操作)的问题,比较安全计数器和非安全计数器结果即可:
原子(atomic)本意是“不能被进一步分割的最小粒子”,原子操作意为“不可被中断的一个或一系列操作”。
处理器实现原子操作方式:
(1) 总线锁:处理器提供一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,该处理器就可以独占共享内存。
书中用i++操作(读改写操作)来解释,如果i=1,进行两次i++操作,期望结果是3,但有可能结果是2,原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行加1操作,然后分别写入系统内存中。这样两次i++操作结果是2。
(2) 缓存锁:通过缓存锁定来保证原子性。
缓存锁定:内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言LOCK#信号(总线锁),而是修改内部的内存地址,并允许他的缓存一致性机制来保证操作的原子性。
缓存一致性:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是否过期,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存中。
两种情况不能使用缓存锁定:
1.当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行(cache line)时,处理器会使用总线锁定。
2.有些处理器不支持缓存锁定。
JAVA中实现原子操作:锁和循环CAS
锁:偏向锁、轻量级锁和互斥锁等。
循环CAS:使用处理器的CMPXCHG指令实现,自旋CAS的思路就是循环执行CAS操作直达成功为止。
CAS操作3大问题:
1. ABA问题:值原来是A,变成了B,再变成A,那么CAS检查值时没有发生变化,但实际发生了。ABA问题解决思路就是加版本号AtomicStampedReference,这个类的compareAndSet方法的作用是先检查当前引用是否是预期引用,并检查当前标志是否是预期标志。全部相等才会做更新操作。
2.循环时间长开销大。
3.只能保证一个共享变量的原子操作:可以使用共享变量,AtomicReference类可保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
以下代码说明了多处理器i++(读改写操作)的问题,比较安全计数器和非安全计数器结果即可:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by bai020 on 2018/1/24.
*
* @author:bai020
* @Description:
* @Date:Created in 19:01 on 2018/1/24.
*/
public class CASTest {
private AtomicInteger atomicI = new AtomicInteger(0);
private int i1 = 0;
/**
* i++操作(读改写操作)来解释,如果i=1,进行两次i++操作,期望结果是3,但有可能结果是2,
* 原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行加1操作,然后分别写入系统内存中。这样两次i++操作结果是2
* @param args
*/
public static void main(String[] args) {
final CASTest cas = new CASTest();
long start = System.currentTimeMillis();
//创建线程list
List<Thread> ts = new ArrayList<>(1000);
for (int j = 0; j < 1000; j++) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
cas.count();
cas.safeCount();
}
});
ts.add(t);
}
//所有线程启动
for (Thread t : ts) {
t.start();
}
//等待所有线程执行完
for (Thread t : ts) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("非线程安全计数器结果:" + cas.i1);
System.out.println("CAS线程安全计数器结果:" + cas.atomicI);
System.out.println("处理时间:" + (System.currentTimeMillis() - start));
}
/**
* 线程安全的计数器,原子类通过CAS自旋保证线程安全
* 输出结果为10000000
*/
private void safeCount(){
for(;;){
int i = atomicI.get();
boolean suc = atomicI.compareAndSet(i, ++i);
if(suc){
break;
}
}
}
/**
* 非线程安全的计数器
* 结果少于10000000
*/
private void count(){
i1++;
}
}