总线锁(CPU总线):
CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性.
缓存锁:
频繁使用的内存会缓存在处理器的L1,L2和L3高速缓存里,那么原子操作就可以直接在处理器内部缓存中进行,并不需要声明总线锁。
所谓“缓存锁定”就是如果缓存在处理器缓存行中内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制(当某块CPU对缓存中的数据进行操作了之后,就通知其他CPU放弃储存在它们内部的缓存,或者从主内存中重新读取)来保证操作的原子性.
JAVA原子操作:
在java中可以通过锁和循环CAS(compare and swap)的方式来实现原子操作。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class T {
public static void main(String[] args) {
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
threads.add(new Thread(new MyThread()));
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
}
}
System.out.println("counter_i:" + MyThread.counter_i);
System.out.println("counter_volatile:" + MyThread.counter_volatile);
System.out.println("counter_automic:" + MyThread.counter_cas.get());
System.out.println("counter_locker:" + MyThread.counter_locker);
}
}
class MyThread implements Runnable {
static int counter_i;
static volatile int counter_volatile;
static AtomicInteger counter_cas;
static int counter_locker;
static {
counter_cas = new AtomicInteger(0);
counter_i = 0;
counter_volatile = 0;
counter_locker = 0;
}
@Override
public void run() {
// 未保证原子型操作
counter_i++;
// 未保证原子型操作
counter_volatile++;
int i;
do {
// 保证了原子型操作
i = counter_cas.get();
} while (!counter_cas.compareAndSet(i, ++i));
// 保证了原子型操作
synchronized (MyThread.class) {
counter_locker++;
}
}
}
分析:
线程中i++可拆解成如下操作:
a. 从主存复制变量到线程本地内存
b. 读取线程本地内存i
c. 线程本地内存i+1
d. 写回线程本地内存
e. 用线程本地内存数据刷新主存相关内容
直接i++有很多种case会出现问题
分析略
volatile i ++
虽然告诉JVM当前变量在寄存器/高速缓存(工作内存)中的值是不确定的,需要从主存中读取), 使修改对其他线程可见,但是仍然没有作到i++的原子操作. volatile无法保证复合操作的原子性。
AtomicInteger 循环使用CAS
基于处理器CMPXCHG,保证了原子性. Java的AtomicInteger使用上面提到的系统级别的总线锁和缓存锁来保证原子操作。
AtomicInteger最终调用
//object:操作的对象
//address:操作对象的属性地址
//expected:预期值
//newValue:新值
compareAndSwapInt(Object object, long address, int expected, int newValue)
CAS有几个缺点:
- ABA:
例如有链表 A->B
线程1 compareAndSet(A,B), compare A 之前, 线程2 将链表结构变为A->C,线程1compare A OK(A地址未变化),并Swap A和 B,链表结构变为B,和预期的B->A不同.
解决办法:使用版本戳,java 中的AtomicStampedReference实现了该功能。 - 循环时间长
- 只能操作一个变量
解决办法:将多个变量封装在一个对象中,使用AtomicReference<V>。
synchronized 对i++操作整体加锁,保证了原子性.
synchronized关键字强制实施一个互斥锁,使得被保护的代码块在同一时间只能有一个线程进入并执行。当然synchronized还有另外一个 方面的作用:在线程进入synchronized块之前,会把工作存内存中的所有内容映射到主内存上,然后把工作内存清空再从主存储器上拷贝最新的值。而 在线程退出synchronized块时,同样会把工作内存中的值映射到主内存,但此时并不会清空工作内存。这样一来就可以强制其按照上面的顺序运行,以 保证线程在执行完代码块后,工作内存中的值和主内存中的值是一致的,保证了数据的一致性!
参考资料:
原子操作的实现原理
关于单CPU,多CPU上的原子操作
聊聊并发(五)——原子操作的实现原理
CAS原理分析
java 里面保留字volatile及其与synchronized的区别
网友评论