synchronized与CAS
- Synchronized 原理
- 加锁工作过程
- 一些优化
- CAS
- 实现原子类
- 小结
Synchronized 原理
- synchronized 既可以是乐观锁, 也可以是悲观锁.
- synchronized 既可以是轻量级锁, 也可以是重量级锁.
- synchronized 重量级锁是由系统的互斥锁实现的; 轻量级锁是基于自旋锁实现的.
- synchronized 是非公平锁(不会遵守先来后到).
- synchronized 是可重入锁(内部会记录哪个线程拿到了锁, 记录引用计数).
- synchronized 不是读写锁.
Synchronized的内部实现策略?
可能会产生一系列的"自适应"的过程, 叫做锁升级.
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁.
加锁工作过程
- 偏向锁:
偏向锁
不会真的加锁, 只是做了一个标记.如果有别的线程来竞争了, 才会真的加锁.如果没有别的线程竞争, 就始终不会真的加锁.(能不加就不加) - 轻量级锁: synchronized通过自旋锁的方式实现轻量级锁.
一个线程把锁占据之后, 另一个线程就会按照自旋的方式反复查询当前锁是否被释放了,
但是, 如果这把锁的线程越来越多了(锁竞争更激烈了), 就会升级为重量级锁.
一些优化
- 锁消除: 编译器会智能的判定当前代码是否要加锁, 如果你写了加锁, 实际上没有必要加锁, 就会把加锁操作自动删除掉.
- 锁粗化: 锁的粒度?
我们一般认为, 如果加锁操作中包含的实际要执行的代码越多, 就认为锁的粒度越大.
锁的粒度越小, 并发程度就更高.
for(...) {
synchronized (this) {
count++;
}
}
锁的粒度越大, 效率就越高.
synchronized (this) {
for(...) {
count++;
}
}
CAS
CAS(Compare and swap).
假设内存中的原数据V, 旧的预期值A, 需要修改的新值B.
1. 比较A与V是否相等.
2. 如果比较相等, 将B写入V.
3. 返回操作是否成功.
使用CAS伪代码来辅助理解CAS的工作流程, 不是原子性代码.
// address: 内存地址. expectValue: 寄存器中的值. swapValue: 相等就替换的值
boolean CAS(address, expectValue, swapValue) {
if(&address == expectValue) {
&address = swapValue;
return true;
}
return false;
}
但是这段代码逻辑, 是通过一条cpu指令完成的, 具备原子性, 就给我们编写线程安全代码, 打开了新大门.
实现原子类
标准库中提供了一组原子类, 最典型的是AtomicInteger类.
有两个构造方法.
// 设置初值为0
AtomicInteger();
// 设置初值为initialValue
AtomicInteger(int initialValue);
提供了一系列方法:
// 前置++
public final int getAndIncrement() {
Atomically increments by one the current value.
Returns:
the previous value
}
// 前置--
public final int getAndDecrement() {
Atomically decrements by one the current value.
Returns:
the previous value
}
// 后置++
public final int incrementAndGet() {
Atomically increments by one the current value.
Returns:
the updated value
}
// 后置--
public final int decrementAndGet() {
Atomically decrements by one the current value.
Returns:
the updated value
}
public class Demo28 {
// 设置初值为0
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count.getAndIncrement();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count.getAndIncrement();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(count);
}
}
// 结果是10000, 是原子性的.
下面使用伪代码实现原子类:
class AtomicInteger {
private int value;
// 前置++
public int getAndIncrement() {
int oldValue = value;
while ( CAS(value, oldValue, oldValue+1) != true) {
oldValue = value;
}
return oldValue;
}
}
// oldValue, value, oldValue+1 都是寄存器中的值
// 如果value和oldValue相等, 就return oldValue
// 如果不等, 就把oldValue = value;
为啥会出现oldValue != value的情况呢??
因为很可能出现有别的线程穿插在两段代码之间, 把它修改了.
以上的图很好的描绘了如何修改代码的…
当两个线程并发的执行++操作时,如果不加任何限制,其一,就会出现串行化;
其二, 就会出现穿插, 会使结果出现问题.
通过加锁保证线程安全,强制避免穿插.
而原子类CAS保证线程安全,借助CAS来识别当前是否出现了穿插情况,如果没穿插,此时直接修改.
如果穿插了,就会重新回去内存中的值,再次尝试修改.
多个CAS线程访问内存时,一定会有先后顺序的.
因为多个cpu在操作同一个资源, 也会涉及到锁竞争(指令级别的锁).比synchronized实现的
锁要轻量许多(cpu内部实现的机制).
小结
本文讲述了synchronized的原理, 以及CAS实现原子类.
博主会在下篇博客中更新CAS实现自旋锁, 以及ABA问题和相关面试题.
希望有收获的小伙伴多多支持!