CAS和synchronized原理

Synchronized 原理

  1. synchronized 既可以是乐观锁, 也可以是悲观锁.
  2. synchronized 既可以是轻量级锁, 也可以是重量级锁.
  3. synchronized 重量级锁是由系统的互斥锁实现的; 轻量级锁是基于自旋锁实现的.
  4. synchronized 是非公平锁(不会遵守先来后到).
  5. synchronized 是可重入锁(内部会记录哪个线程拿到了锁, 记录引用计数).
  6. synchronized 不是读写锁.

Synchronized的内部实现策略?
可能会产生一系列的"自适应"的过程, 叫做锁升级.
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁.

加锁工作过程

  • 偏向锁: 偏向锁不会真的加锁, 只是做了一个标记.如果有别的线程来竞争了, 才会真的加锁.如果没有别的线程竞争, 就始终不会真的加锁.(能不加就不加)
  • 轻量级锁: synchronized通过自旋锁的方式实现轻量级锁.
    一个线程把锁占据之后, 另一个线程就会按照自旋的方式反复查询当前锁是否被释放了,
    但是, 如果这把锁的线程越来越多了(锁竞争更激烈了), 就会升级为重量级锁.

一些优化

  • 锁消除: 编译器会智能的判定当前代码是否要加锁, 如果你写了加锁, 实际上没有必要加锁, 就会把加锁操作自动删除掉.
  • 锁粗化: 锁的粒度?
    我们一般认为, 如果加锁操作中包含的实际要执行的代码越多, 就认为锁的粒度越大.
    锁的粒度越小, 并发程度就更高.
for(...) {
	synchronized (this) {
		count++;
	}
}

锁的粒度越大, 效率就越高.

synchronized (this) {
	for(...) {
		count++;
	}		
}

CAS

CAS(Compare and swap).
假设内存中的原数据V, 旧的预期值A, 需要修改的新值B.
1. 比较AV是否相等.
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问题和相关面试题.
希望有收获的小伙伴多多支持!

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值