Synchronized详解

111 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

Synchronized详解

线程安全问题?

  • 应用程序在并发执行性的时候,对临界资源可能会造成线程安全问题,由于没办法控制线程的执行过程,所以需要对访问临界资源的线程进行序列化控制;
  • 一般的做法就是对临界资源加锁操作,synchronized关键字和JUC的加锁的方式,本文介绍synchronized的加锁方式;
  • 保证的并发执行的原子性、可见性、有序性

使用方法

  • 同步方法

    public synchronized void  getWay(){
    	..代码逻辑..
    }
    同步实例方法,锁住当前实例,当有线程竞争执行此方法逻辑需要等待;
    
  • 静态方法

    public static synchronized void  getWay(){
    	..代码逻辑..
    }
    同步静态方法,锁住当前类对象,当有线程竞争执行此方法逻辑需要等待;
    
  • 同步代码块

      public synchronized(obj) {
    	..代码逻辑..
    }
    同步代码块,锁住当前obj对象,当有线程竞争执行此方法逻辑需要等待;锁粒度最低
    

原理介绍

​ synchronized是基于操作系统底层Mutex lock(互斥锁)来实现的,属于重量级锁,性能不高;每个jvm对象内部都有一个Monitor(监视器锁),对于加了synchronized的代码块,执行的时候会在代码块的两端分别加上monitor.enter和monitor.exit命令,用来标记对当前资源的加锁和释放;

​ 之所以说它性能不高是因为jvm会调用底层操作系统的Mutex命令,阻塞线程会被挂起,等待重新被唤醒,其中会涉及内核态和用户态的转换,比较耗费性能;

​ synchronized是可重入锁,同一个线程可以多次获取资源;

​ jvm1.6版本之后对synchronized做了升级改造,引入了偏向锁、轻量级所、自适应自选、锁消除等手段来优化;

​ 一般来说内置锁共有四种状态:无 锁——》偏向锁——》轻量级锁——》重量级锁,锁的膨胀升级过程随着并发量的增加而逐步升级,状态不可逆;

** (๑•̀ㅂ•́)و✧下面分别来介绍一下synchronized锁的升级优化过程:**

  • 偏向锁

​ 是指临界资源一直在被同一个线称不断的操作,如果对象的Markword已经记录了此时资源正在偏向锁状态,那么当当同一个线程来访问资源时,会先判断此时markword中锁保存的偏向线程的id是自己的话,就可以直接进入同步代码块,不需要再去申请锁的操作,提升锁的性能;

​ 偏向锁是默认开启的,但是在jvm启动后偏向锁并不会立马开启,而是会等等待一会;不想等待,可以手动开启0延时;-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0;

​ 在锁竞争稍微激烈一些的情况下,偏向锁就显得很鸡肋,一旦有别的线程参与竞争,偏向锁会升级成轻量级锁,就需要把偏向锁撤销,无辜耗费了资源,一定程度上也拉低了性能,因此在可预知的情况下,也可关闭偏向锁 -XX:-UseBiasedLocking

  • 轻量级锁

​ 在向轻量级锁膨胀过程中,也就是意味着存在多个线程争抢资源的情况,当其中一个非markword记录的偏向锁的线程id不是当前抢锁线程,则会尝试把当前偏向锁撤销,升级成轻量级锁;

​ 此时哪个线程抢到锁,会把markword的锁记录指向当前线程所在栈帧的的锁记录(线程在抢锁的时候。会把本地线程栈栈帧建立一个锁记录,用于记录对象锁的信息);

​ 其他未获得锁的线程将会通过cas自旋的方式取锁---也就是把对象头中的所记录更新成自己的栈帧中的锁记录;

​ 如果竞争实在激烈,在自旋等待的时间内还未获得锁,表明此时线程强锁过于激烈,将会升级成重量级锁;

​ 自旋锁不会把线程阻塞挂起,而是空自旋等待;

​ 轻量级锁在自旋时可以对旋转时间进行配置,默认是自旋10次,这种称之为普通自旋;

​ 可以通过-XX:PreBlockSpin来配置自选次数;当然虽然说是可配置,但是不建议修改的自旋次数过大,毕竟空自旋也是会浪费cpu的;

​ 还有一种叫做自适应自旋,大概的描述就是自旋的时间不固定,它内部会根据对同一个对象锁获得的时间来进行动态的调整;如果一个对象锁的获得锁的时间比较长,那么它自选等待的时间相对应的儿也会比较长;

  • 重量级锁

​ 监视器(Monitor):重量级锁跟对象锁的监视器有关,每个对象都有Monitor监视器,因此几乎每个对象都可以被用来当做对象所=锁使用;它就相当于对象的获取凭证一样,一旦有线程拿到了这个凭证,当前monitor立刻会被锁定,当前线程就拥有了对获得凭证的当前对象操作的一区且权利;直至当前线程释放该凭证;它保证了在任意时间点,只有一个线程能操作该对象;(具体关于monitor,后面详解)

​ 关于重量级锁的开销很大,上面也描述了,他底层调用操作系统的Mutex命令,会造成用户态切换到内核态,这种切换简单来说就是用户态为了能访问到内核态的接口资源会通过系统调用的方式,系统调用就会产生用户跟内核的切换,比较耗时;

【🦐🦐🦐压压惊(>^ω^<)喵与文章无瓜😄😄😄】