Java中synchronized原理与CAS

1.synchronized的特性

1.synchronized初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动转换为悲观锁策略
2.synchronized 开始是一个轻量级锁. 如果锁冲突比较严重, 就会变成重量级锁.
3.synchronized 中的轻量级锁策略大概率就是通过自旋锁的方式实现的
4.synchronized是一种不公平锁(不会遵守先来后到,锁释放后,哪个线程能获取到锁,不确定)
5.synchronized是一种可重入锁(内部会记录哪个线程拿到了锁,记录引用计数)
6.synchronized不是读写锁

2.synchronized的使用

synchronized用于实现Java中实现线程同步,确保在同一时间只有一个线程可以被synchronized修饰的代码块或方法.

2.1修饰实例方法

修饰实例方法:对当前对象实例进行加锁,进入同步代码块要先获取当前对象的实例.

public class SynchronizedExample {
    private int count = 0;

    // 同步方法
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

2.2 修饰代码块

指定加锁对象,对给定对象/类加锁synchronized(this/object)表示进入同步代码块要先获得给定的对象的锁.synchronized(类.class)表示进入同步代码块要先获得当前class的锁

//锁当前对象
public class SynchronizedExample {
    private int count = 0;
    private Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}
//锁类对象
public class demo2 {
    public void method() {
        synchronized (demo2.class) {
        }
    }
}

2.3 修饰静态方法

修饰静态方法:也就是给当前类加锁,会作用于类的所有的对象实例,进入代码块要先获得当前对象class的锁,因为静态成员不属于任何一个实例对象,是类成员(static表示这是一个该类的静态资源,不管new了多少个对象,只有一份)

public class demo3 {
    private static int count = 0;

    // 静态同步方法
    public static synchronized void increment() {
        count++;
    }

    public static synchronized int getCount() {
        return count;
    }
}

3.synchronized的锁优化

synchronized的锁优化会涉及到几种状态,分为 无锁、偏向锁、轻量级锁、重量级锁状态,这些状态会根据线程的竞争情况进行动态调整.

3.1 偏向锁

初始时,锁会处于偏向状态.偏向锁不是真的 “加锁”, 只是给对象头中做一个 “偏向锁的标记”, 记录这个锁属于哪个线程.当一个线程获取了偏向锁,并且没有其他线程竞争时,该线程会一直持有这个锁(避免了加锁解锁的开销).但当有其他线程尝试获取锁时,偏向锁会失效,锁升级为一般的轻量级锁状态.

3.2 轻量级锁

随着其他线程进入竞争,偏向锁状态被消除,进入轻量级锁状态(自适应的自旋锁),
此处的轻量级锁就是通过CAS来获取实现的:

  • 通过CAS检查并更新一块内存
  • 如果更新成功,就说明获取到锁.
  • 如果更新失败,就会被认为所锁被占用,继续自选的方式等待

自旋锁的操作会不断的反复查询当前锁的状态是不是被释放了,如果被释放了,就能立马获取到锁,没释放就继续自旋,当达到了一定的时间/重试次数,就不会自旋了,synchronized会自适应

3.3 重量级锁

当轻量级锁竞争激烈或CAS操作多次尝试失败时,锁会升级为重量级锁。这时候,锁会使用操作系统提供的互斥量来保证多个线程之间的互斥性。重量级锁会导致线程阻塞和切换,因为线程在获取不到锁时会进入阻塞状态,直到锁被释放。

3.4 锁的其他优化操作

3.4.1 锁消除

锁消除是指JVM在即时编译器优化阶段对代码进行分析,如果发现某些同步代码块使用的锁对象在一定条件下不可能存在竞争,那么就会将这些锁完全消除掉,以减少不必要的同步开销,锁消除通常发生在局部作用域内.

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");
此时每一个append的调用都会涉及加锁和解锁,但是在单线程中执行这个代码是没有必要的,会浪费资源开销.

3.4.2 锁粗化

锁粗化是指JVM在编译器优化时将多个连续的同步块合并为一个更大的同步块,从而减少线程在同步块边界上的竞争和同步开销,当一系列操作都是对同一对象进行同步,彼此之间没有竞争关系时,JVM会把这些操作合并为一个更大的代码块,减少加锁和解锁的开销.
在这里插入图片描述

4. 什么是CAS

CAS是Compare and Swap(比较并交换)的缩写,它的实现原理是:

CAS 操作包含三个参数:内存位置V(通常是一个变量的内存地址)、旧的期望值A和新值B

  1. 比较 A 与 V 是否相等。(比较)
  2. 如果比较相等,将 B 写入 V。(交换)
  3. 返回操作是否成功。

CAS保证原子性的关键在于它是一个原子操作,是通过一条cpu指令完成的.

4.2 CAS的作用

  1. 实现原子类:CAS通过java.util.concurrent.atomic 包中的原子类来实现,这些原子类提供了一些原子性的操作.比如AtomicInteger自增等.
  2. 实现自旋锁:通过循环使用CAS操作来获取锁,避免了线程的阻塞和减少了线程切换开销.

4.3 CAS的ABA问题

ABA问题指的是在多线程环境下,一个值从A变成B,再从B变成A,如果一个线程只关注数值是否相同,就会误判没有发生变化.
举个例子:假设银行初始值存款为100,要取款50元,但是我不小心按了2次,此时就会执行业务两次,第一次会判定账户余额是否和期望值相同,再成功取出50元,但是中途有人给我转入了50元,在第二次判定的时候,账户余额会和期望值相同,会再次取出50元.此时就是典型的ABA问题.

针对解决措施我们就引入了版本号,只需要给修改的数据引入版本号,在CAS比较当前值和旧值时候,查看版本号是否复合预期,如果发现版本号和之前读到的版本号一致,就执行修改操作,并让版本号自增,如果发现当前版本号比之前读的版本号大,就认为操作失败,其实引入版本号的目的就是为了查看内存值是否被多次修改.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值