多线程--synchronized

本文深入探讨Java中的锁机制,包括悲观锁和乐观锁的概念,详细解析synchronized关键字及其实现原理,从轻量级锁到重量级锁的升级过程,以及自旋锁的应用。同时,介绍了CAS操作的ABA问题及其解决方案。
摘要由CSDN通过智能技术生成

多线程--synchronized

  1. 锁的类型:锁从宏观角度上分为悲观锁和乐观锁

    1. 悲观锁:悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock
    2. 乐观锁:乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁。乐观锁多由CAS实现
      1. CAS(compare and swap)大概示意图:
        在这里插入图片描述
      2. CAS的ABA问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?如果在这段时间曾经被改成B,然后又改回A,那CAS操作就会误以为它从来没有被修改过。
      3. ABA问题解决方案:从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
      4. 跟入CAS实现源码可以找到 cmpxchg1 %1,(%3) 这条汇编指令,所以我们可以推断CAS在CPU层面上有实现和支持,但是该汇编指令是没有原子性的,即将数据往回写的过程中有可能出现并发问题,所以CAS原子性的真正实现是另一条指令:lock cmpxchg(锁总线),在硬件层面上实现了CAS的原子性
  2. Java中的锁

    1. JDK早期,synchronized叫做重量级锁(悲观锁),因为申请资源必须通过kernel,系统调用,即若想持有锁必须通过内核态来完成,什么是用户态和内核态?
      1. 用户态:当一个进程在执行用户自己的代码时处于用户运行态(用户态),此时特权级最低,为3级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。
      2. 内核态:当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),此时特权级最高,为0级。执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。
      3. 用户运行一个程序,该程序创建的进程开始时运行自己的代码,处于用户态。如果要执行文件操作、网络数据发送等操作必须通过write、send等系统调用,这些系统调用会调用内核的代码,进入到内核态来执行,完成后再回到用户态,这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。
    2. 但是由于重量级锁会造成线程排队(串行执行),且会使CPU在用户态和核心态之间频繁切换,所以代价高、效率低。所以从JDK1.5开始,为了提高效率,不会一开始就使用重量级锁,JVM在内部会根据需要,按如下步骤进行锁的升级
      在这里插入图片描述
      1. 无锁(markword状态:001)
      2. 偏向锁(markword状态:101)
        1. 它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁,这时锁对象的markword会记录该线程的ID。
        2. 可以通过参数-XX:BiasedLockIngStartupDelay=0 来设置偏向锁的启动延时(默认值为4000,即4秒延时)。为什么要设置启动延时:当一个对象刚创建完就会有很多资源去争抢并发时,启动轻量锁就没有意义。比如JVM启动时就会有很多同步线程,对这些线程启动偏向锁没有意义。
        3. 如果有多个线程开始竞争此锁对象,则会升级为轻量级锁
      3. 轻量级锁(markword状态:00)
        1. 乐观锁,由偏向锁升级而来,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁
        2. 使用LockRecord指针来竞争锁对象。在自旋锁状态,每个线程在自己的线程栈中生成LockRecord,尝试将自己锁记录的指针写入到锁对象的markword中,成功则认为该线程持有锁,重入锁也是通过LockRecord,重入几次生成几个LockRecord
        3. 轻量级锁和自旋锁的关系:
          1. 锁自旋是一种锁竞争机制,假设在一段时间内比如一个时钟周期内能获得锁,虚拟机会进行一次赌博,在这段时间内一直尝试加锁;而轻量级锁是一种状态
          2. 线程首先会尝试通过CAS获取锁,失败后通过自旋锁来尝试获取锁,再失败锁就膨胀为重量级锁。所以轻量级锁状态下可能会有自旋锁的参与(CAS将对象头的标记指向锁记录指针失败的时候)
      4. 重量级锁(markword状态:10)
        1. 悲观锁,有线程超过10次自旋,或者自旋线程数超过CPU核数的一半,轻量级锁会膨胀为重量级锁,JDK1.6之后,加入自适应自旋 Adapative Self Spinning , JVM自己控制
        2. 由C++实现,会生成一个C++对象(ObjectMonitor),里面封装了许多与线程相关的容器
          1. Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中
          2. Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中
          3. Wait Set:哪些调用wait方法被阻塞的线程被放置在这里
          4. OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck
          5. Owner:当前已经获取到所资源的线程被称为Owner
          6. !Owner:当前释放锁的线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值