处理器如何实现原子操作
使用总线锁保证原子性
总线锁 就是使用处理器提供的一个Lock # 信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占共享内存。
使用缓存锁保证原子性
所谓缓存锁定,是指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作写会到内存时,处理器不在总线上声言Lock # 信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存预期数据,当其他处理器写回已被锁定的缓存行的数据时,会使缓存行无效。
不能使用缓存锁定的情况
第一种情况是:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,则处理器会调用总线锁定。 第二章情况是:有些处理器不支持缓存锁定。
java如何实现原子操作
从Java 1.5 开始,JDK的并发包里提供了一些类来支持原子操作,这些原子包装类还提供了有用的工具方法,比如以原子的方法将当前值自增1和自减1.
package com.control;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by cxx on 2018/2/1.
* java 如何实现原子操作:通过锁和循环CAS的方式来实现原子操作
*/
public class ControlCount {
//一个基于CAS线程安全的计数器方法safeCount 和一个线程非安全的计数器count
private AtomicInteger atomicI = new AtomicInteger(0);
private int i = 0;
public static void main(String[] args) {
final ControlCount cas = new ControlCount();
List<Thread> ts = new ArrayList<Thread>();
// 添加100个线程
for (int j = 0; j < 100; j++) {
ts.add(new Thread(new Runnable() {
public void run() {
// 执行100次计算,预期结果应该是10000
for (int i = 0; i < 100; i++) {
cas.count();
cas.safeCount();
}
}
}));
}
//开始执行
for (Thread t : ts) {
t.start();
}
// 等待所有线程执行完成
for (Thread t : ts) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("非线程安全计数结果:"+cas.i);
System.out.println("线程安全计数结果:"+cas.atomicI.get());
}
/***
* 使用CAS实现线程安全计数器
*/
private void safeCount(){
for (;;){
int i = atomicI.get();
boolean suc = atomicI.compareAndSet(i,++i);
if (suc){
break;
}
}
}
/***
* 非线程安全计数器
*/
private void count(){
i++;
}
}
使用CAS实现原子操作的三大问题
1.ABA问题
产生的原因:CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成B,又变成了A,那么使用CAS进行检查时会发现他的值没有发生变化,但是实际上确变化了。
解决思路:在变量前面追加上版本号,每次变量更新的时候把版本号加1,从Java1.5开始,JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并检查当前标志是否等于预期标致,如果全部相等,则以原子方式将该引用和该标致设置为给定的更新值。
2.循环时间长开销大。 自旋CAS如果长时间不成功,会给cpu带来非常大的执行开销。如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升。pause知道有两个作用:第一,他可以延迟流水线执行指令(de-pipeline),使cpu不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零;第二,它可以避免在退出循环时因内存顺序冲突(Memory Order Violation) 而引起CPU流水线被清空,从而提供CPU的执行效率。
3.只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原则操作,但是对于多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。
总结
原子操作的保证只有 CAS和 锁两种机制,CAS只能使用一个共享变量,多个变量要用锁。
面试问题示例
public class ControlCount {
private int i;
@RequestMapping("/request")
public String downQuestionByCode(HttpServletRequest request, ModelMap model){
return i++;
}
}
分析上面代码存在的问题。