并发编程之synchronized

synchronized

    • 局部锁
    • 全局锁
  • jdk1.6后对synchronized的优化
    • 重量级锁
    • synchronized优化
    • 偏向锁
    • 轻量级锁
    • 锁粗化
    • 锁消除

我们说到并发编程我们很容易想到synchronized关键字,sychronized是锁机制中比较常见的同步锁,synchronized是基于jvm级别的锁,由jvm管理锁的获取与释放,使用者无需担心锁不释放问题。synchronized可以修饰方法,传入对象。

局部锁

synchronized修饰非静态方法与传入对象时锁的对象是非静态Object,不同的Object之间的锁互不影响,锁范围是当前对象。这时候synchronized锁类型为局部锁。

全局锁

sychronized修饰静态方法或传入的对象是静态对象或单例对象时是全局锁,synchronized修饰静态方法时锁对象是Class,同一个类的Class在jvm中只有一个,因此构成全局锁。不同类之间的全局锁互不影响。如果传入synchronized的对象时静态对象或单例对象时,由于在jvm中只存在一个,因此构成全局锁。

jdk1.6后对synchronized的优化

重量级锁

synchronized是基于锁对象内部一个监视器锁(monitor)实现的,监视器锁的本质是依赖操作系统的Mutex Lock实现的,当系统检查到锁是该类型锁后会把等待想要获取锁的线程阻塞,阻塞或唤醒一个线程时需要操作系统帮忙,从用户状态切换到内核状态,这种切换开销非常大,花费的时间可能比用户执行的代码所花的时间还长。因此synchronized本身是一个重量级锁。

在java对象头中记录了Mark Work信息,指向数据对象的指针信息,数组长度信息。其中Mark Work记录了hashCode,gc分代年龄,锁信息。其中锁信息主要为锁标志位,是否偏向,线程id,指向栈中锁记录的指针。
锁标志位 是否偏向
01 0 无锁
01 1 偏向锁,记录拥有偏向锁的线程id
00 轻量级锁,记录指向获得轻量级锁的线程栈的所记录指针
10 重量级锁,阻塞未获取锁的线程,记录指向互斥量(重量级锁)指针

synchronized优化

偏向锁

根据经验,大部分时候代码的执行是不存在竞争的,如果采用重量级锁就会产生不必要的锁获取开销。因此可以先采用偏向锁,检查锁对象头的锁信息是否为无锁状态,如果是则使用cas尝试将锁对象头的偏向锁线程id修改为当前线程id,如果修改成功,获取偏向锁成功,修改锁偏向标志为1,执行同步代码块。如果当前线程修改锁对象头的偏向锁线程id为自身id失败则说明有其他线程获取了偏向锁,由于偏向锁不会自己释放,在其他线程竞争时并且达到安全点(无代码在执行时,获取偏向锁的线程执行完了代码)释放。当前线程尝试执行撤销偏向锁,如果偏向锁在安全点则释放偏向锁,恢复无锁状态。当前线程重新执行获取偏向锁操作。如果当前线程执行偏向锁释放时偏向锁未达到安全点,则撤销偏向锁失败,如果获得偏向锁的线程正在运行,则暂停获取偏向锁线程,修改锁对象头所标志位为00,升级锁为轻量级锁,记录获取轻量级锁的线程栈的记录指针。偏向锁经常在jvm启动几秒种后才激活,可以使用XX:BiasedLockingStartupDelay=0 关闭延迟。如果确认锁经常处于竞争状态,可以通过-XX:-UseBiasedLocking = false关闭偏向锁,。

轻量级锁

锁升级为轻量级锁后,虚拟机会在当前线程栈中创建一块名为Lock Record的空间,用来存储锁对象Mark Work的拷贝。拷贝成功后使用cas尝试将Mark Work的所记录指针指向Lock Record,如果执行成功获取轻量级锁。如果执行不成功重复循环尝试获取轻量级锁。达到一定次数后仍无法获取轻量级锁则将Mark Work锁标志位更新为10,锁升级为重量级锁。获取锁的线程进入阻塞。获取轻量级锁的线程执行完同步代码后使用cas尝试将Lock Record替换回到Mark Work,如果成功释放轻量级锁,如果失败说明所已经升级为重量级锁,当前线程释放锁并唤醒一个等待锁的线程。
锁状态 25 bit 4bit 1bit 2bit
23bit 2bit 是否是偏向锁 锁标志位
轻量级锁 指向栈中锁记录的指针 00
重量级锁 指向互斥量(重量级锁)的指针 10
GC标记 空 11
偏向锁 线程ID Epoch 对象分代年龄 1 01

锁粗化

减少不必要的紧连在一起的lock unlock操作,扩大成更大范围的锁。

锁消除

通过编译器逃逸分析不存在数据共享竞争的锁进行消除。

你可能感兴趣的:(并发,synchronized,并发编程)