Synchronized

  1. public void produce() {
  2. synchronized (this) {
  3. while (mBuf.isFull()) {
  4. try {
  5. wait();
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. mBuf.add();
  11. notifyAll();
  12. }
  13. }

到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋锁消除锁粗化轻量级锁偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。

Synchronized可见性

工作内存和主内存

JMM中关于synchronized有如下规定,线程加锁时,必须清空工作内存中共享变量的值,从而使用共享变量时需要从主内存重新读取;线程在解锁时,需要把工作内存中最新的共享变量的值写入到主存,以此来保证共享变量的可见性。(ps这里是个泛指,不是说只有在退出synchronized时才同步变量到主存)

作者:minato丶 链接:https://www.zhihu.com/question/48313299/answer/1166823164 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

System.out.println内部有synchronized

对象头

Synchronized总结 - 图1
Java对象的构成:

  1. 对象头

Java对象头构成:

  1. MarkWord
  2. 指向类的指针
  3. 数组长度(只有数组对象才有)
    1. 实例数据
    2. 对其填充字节

MarkWord

64位,所以引入基本类型,节省内存。
Synchronized总结 - 图2
image.png

上锁条件

  • 偏向锁:只有一个线程进入临界区;
  • 轻量级锁:多个线程交替进入临界区
  • 重量级锁:多个线程同时进入临界区。

    偏向锁【markword记录线程id】

  • 会在Mark Word里存储锁偏向的线程ID

  • 偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可,平时只判断markword中的线程ID是否为当前ID即可,而轻量级锁每次都要CAS检查。
  • 线程执行完后,也不修改markword
  • 可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。
  • 当调用对象hashCode()方法时会撤销对象的偏向锁状态【因为偏向锁markword中没有hashcode,换为轻量级锁后写回hashcode到markword】

那为什么轻量级锁和重量级锁可以调用hashcode呢?
因为轻量级锁hashcode会存在锁记录里,重量级锁会存在monitor里。

  • 调用wait/notify也会撤销偏向锁
  • 撤销偏向锁升级轻量级锁需要STW

    轻量级锁【Lock Record锁记录】

    轻量级锁原理

  1. 先在线程栈中创建一个锁记录(Lock Record)

    锁记录就是用来记录谁持有我的锁

image.png

  1. 通过CAS交换markwordmarkword存放锁记录的地址,锁记录中存放原来的markword

CAS交换锁记录地址成功,就算加锁成功。
image.png

  1. 重入锁会再创建一个锁记录

image.png

轻量级锁为什么轻量

轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record
不用分配monitor对象,monitor对象中还有一些队列。

轻量级锁为什么要膨胀为重量级锁

因为thin-lock没有足够空间来存储额外状态,所以在遇到某个锁实际上有contention的情况下,如果不膨胀,实质上就要迫使所有在等待这把锁的线程都要spin-wait,有可能会非常非常低效。 thin-lock膨胀到fat-lock(HotSpot VM里的ObjectMonitor)就有了更多存储空间,可以存下诸如native mutex之类的OS提供的同步原语对象的指针,可以在contended的情况下更高效地实现线程等待。

作者:RednaxelaFX 链接:https://www.zhihu.com/question/41930877/answer/136699311 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

轻量级锁为什么要膨胀? - 知乎
为了避免无效的自旋,不如升级为重量级锁阻塞。【重量级锁有队列,让线程去排队等着吧】

何时锁膨胀(锁膨胀的条件)

Synchronized总结 - 图7

1.偏向锁膨胀:线程A持有锁,其他线程请求过锁就会膨胀,也就是说对于锁来说,只要有超过一个线程请求过锁,注意是请求过,偏向锁就会膨胀成轻量级锁。(就是来一个线程,看到markword中的线程id不是自己的线程id就升级为轻量级锁) 2.轻量级锁膨胀:线程A在运行中持有锁,线程B竞争锁,线程B会首先自旋自旋超时之后会膨胀成重量级锁,注意条件是发生竞争

作者:刘自在 链接:https://www.zhihu.com/question/53826114/answer/382458685 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

重量级锁【Monitor对象】

  • 需要内核态切换

markword不再指向锁记录,转而指向Monitor对象。
等待线程放入EntryList中
重量级锁也有锁记录
image.png

已经加了轻量级锁,如何变为重量级锁的?

image.png

java中级程序员必会的教程,解密JVM【黑马程序员出品】_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili

  1. 线程1先在markword上加了轻量级锁
  2. 线程2发现加轻量级锁失败,就修改markword为重量级锁【此时线程1上还认为markword时轻量级锁】
  3. 当线程1释放锁时,发现已经变为重量级锁了。就按照重量级锁的方式去释放,然后唤醒重量级锁的等待队列。

    Monitor对象每个Object都关联一个Monitor对象

    Synchronized总结 - 图10
    阻塞线程先进入 EntryList
    获取锁后,修改owner。指向占用对象锁的thread。
    调用obj.wait()后,进入WaitSet

    自适应自旋优化

    image.png

    疑问:自旋到底是轻量级锁状态下还是重量级锁状态下的?

    但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级 不可不说的Java“锁”事 - 美团技术团队

参考