zoukankan      html  css  js  c++  java
  • synchronized

    java中主要有两种加锁机制

    (1)sychronized
    (2)lock
    

    两者加锁的原理不同

    synchronized使用了一对字节码指令monitorenter和monitorexit来实现
    lock使用了java代码和底层调用实现
    

    synchronized锁的状态:

    无锁状态
    偏向锁状态
    轻量级锁状态
    重量级锁状态
    四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,轻量级锁升级到重量级锁时还用到了自旋锁
    

    为什么会有上述4种状态?

    一开始只有重量级锁,但是使用重量级锁,每次都需要线程的阻塞,线程的阻塞又会消耗很大操作系统资源,为了节省系统资源所以设计了其他几种锁
    

    阻塞代价

    java线程是映射到操作系统线程的,每次线程的阻塞和挂起都需要操作系统的配合,
    都需要在用户态和内核态之间切换,会消耗大量的系统资源
    

    自旋锁

    线程在获取锁时发现此锁已被其他线程占用,
    此时线程不直接进入阻塞状态,而是执行一定次数的空循环(等一等),等持有锁的线程释放锁,然后尝试获取锁
    这样就避免用户线程和内核的切换的消耗
    

    自旋锁的优点

    自旋锁尽可能的减少线程的阻塞,
    这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起操作的消耗!
    

    自旋锁的缺点

    如果锁竞争的时间比较长,那么自旋通常不能获得锁,白白浪费了自旋占用的CPU时间。
    这通常发生在锁持有时间长,且竞争激烈的场景中,此时应主动禁用自旋锁。
    

    偏向锁

    适用于大多数情况都是同一线程进入同步代码块的情况
    偏向锁会偏向上一次使用该锁的线程,如果还是上一次的线程进入该同步代码块,
    则会省去加锁过程,也会省去cas过程,
    如果该线程在持有此锁的过程中遇到了其他线程的争用,
    则该线程会挂起,并释放偏向锁,然后升级为轻量级锁
    

    偏向锁优点

    加锁和解锁不需要额外的消耗
    

    偏向锁缺点

    如果线程间存在锁竞争,会带来额外的锁撤销的消耗
    

    轻量级锁

    轻量级锁是由偏向所升级来的
    一个线程在持有偏向锁运行在同步代码块的情况下,当其他线程也要取得该对象锁时,偏向锁就会升级为轻量级锁;
    

    轻量级锁优点

    竞争的线程不会阻塞,提高了程序的响应速度
    

    轻量级锁缺点

    如果始终得不到锁竞争的线程使用自旋会消耗CPU
    

    重量级锁

    重量级锁由轻量级锁升级而来
    一个线程在持有轻量级锁运行在同步代码块的情况下,当其他线程通过自旋也未获得锁时,轻量级锁就会升级为重量级锁
    重量级锁会造成线程阻塞,内核态与用户态切换
    

    重量级锁优点

    线程竞争不使用自旋,节省了自旋消耗的CPU
    

    重量级锁缺点

    线程阻塞,消耗系统资源,响应时间缓慢
    

    偏向锁、轻量级锁、重量级锁适用于不同的并发场景:

    偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。
    轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。
    重量级锁:有实际竞争,且锁竞争时间长。
    

    java对象头

    在32位系统中,一个word占4bytes
    在64位系统钟,一个word占8bytes	
    每一个java对象都至少占两个word(数组占3个word)
    其中第一个word被称为markword,包含了多种不同的信息,其中就包含对象锁相关的信息
    第二个word是一个指针,指向该对象类信息的指针
    

    cas指令

    cas指令是cpu层级的原子性指令
    cas有三个参数(目标地址/值1/值2)
    该指令会比较目标地址的内容和值1是否相等,如果相等,就是用值2替换目标地址的内容
    

    程序进入同步代码块时

    	判断当前锁状态
    	if(无锁){
    		使用CAS加锁
    		如果 CAS 操作成功, 则认为已经获取到该对象的偏向锁, 执行同步块代码
    		如果 CAS 操作失败, 则说明, 有另外一个线程 Thread B 抢先获取了偏向锁。 这种状态说明该对象的竞争比较激烈, 此时需要撤销 Thread B 获                得的偏向锁,将 Thread B 持有的锁升级为轻量级锁。 该操作需要等待全局安全点 JVM safepoint ( 此时间点, 没有线程在执行字节码) 。
    	}else if(偏向锁){
    		则检测 MarkWord 中存储的 thread ID 是否等于当前 thread ID 。
    		如果相等, 则证明本线程已经获取到偏向锁, 可以直接继续执行同步代码块
    		如果不等, 则证明该对象目前偏向于其他线程, 若偏向的线程已退出同步代码块,则CAS加锁,否则升级为轻量级锁
    	}else if(轻量级锁){
    		使用CAS加锁
    		如果 CAS 操作成功,则认为已经获取到该对象的轻量级锁, 执行同步块代码
    		如果 CAS 操作失败,自旋获取轻量级锁,通过自旋一定次数,如果获取到了锁,执行同步代码块,否则,升级为重量级锁                 
    	}else if(重量级锁){
    		则检测 MarkWord 中存储的 thread ID 是否等于当前 thread ID 。
    		如果相等, 则证明本线程已经获取到偏向锁, 可以直接继续执行同步代码块
    		如果不等, 则证明该对象目前偏向于其他线程, 需要撤销偏向锁
    	}
    

    偏向锁的获取过程

    线程进入同步代码块之前,发现当前锁为偏向锁,
    查看偏向锁中的信息,看是否是偏向本线程,如果是,则直接执行同步代码块中的程序,如果不是,则继续向下走
    使用cas尝试修改mark word中的信息,让偏向锁偏向本线程
    若修改成功,则直接执行程序,若修改失败,则升级为轻量级锁
    

    偏向锁的撤销

    通过 MarkWord 中已经存在的 Thread Id 找到成功获取了偏向锁的那个线程, 
    然后在该线程的栈帧中补充上轻量级加锁, 会保存的锁记录(Lock Record),
    然后将被获取了偏向锁对象的 MarkWord 更新为指向这条锁记录的指针
    

    偏向锁的撤销结果有可能是两个

    第一/无状态锁	如果之前持有偏向锁的线程已经退出同步代码块
    第二/轻量级锁	如果之前持有偏向锁的线程还未退出同步代码块
    

    锁优化——减少锁的时间

    不需要同步执行的代码,能不放在同步块里面执行就不要放在同步块内,可以让锁尽快释放;
    

    锁优化——减少锁的粒度

    它的思想是将物理上的一个锁,拆成逻辑上的多个锁,增加并行度,从而降低锁竞争。它的思想也是用空间来换时间;
    ConcurrentHashMap
    LongAdder
    LinkedBlockingQueue
    

    锁的使用情况

    在几乎无竞争的条件下, 会使用偏向锁, 在轻度竞争的条件下, 会由偏向锁升级为轻量级锁, 在重度竞争的情况下, 会升级到重量级锁。
    

    锁的升级过程

    一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是无状态的。
    当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁,偏向第一个线程。
    这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的线程ID,
    之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。
    
    一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象是偏向状态,这时表明在这个对象上已经存在竞争了,
    检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,
    如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,
    (偏向锁就是这个时候升级为轻量级锁的)。如果不需要持有偏向锁,则可以将对象回复成无锁状态,然后重新偏向。
    轻量级锁认为竞争存在,但是竞争的程度很轻,可以稍微等待一下(自旋),另一个线程就会释放锁。 
    但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,
    重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。
    

  • 相关阅读:
    阿里巴巴人力资源挖坑 面试技术挑战题
    •••| 卡牌游戏诞生记 |•••
    JavaScript:js-cookie存取
    乘积最大
    最大的算式
    KMP笔记√//找最大子串,前缀自匹配长度
    牛棚回声
    P1027 木瓜地
    P1026 犁田机器人
    P1023 奶牛的锻炼
  • 原文地址:https://www.cnblogs.com/jis121/p/11032751.html
Copyright © 2011-2022 走看看