Synchronized
public void produce() {
synchronized (this) {
while (mBuf.isFull()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mBuf.add();
notifyAll();
}
}
到了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
对象头
Java对象的构成:
- 对象头
Java对象头构成:
- MarkWord
- 指向类的指针
- 数组长度(只有数组对象才有)
- 实例数据
- 对其填充字节
MarkWord
上锁条件
- 偏向锁:只有一个线程进入临界区;
- 轻量级锁:多个线程交替进入临界区;
-
偏向锁【markword记录线程id】
会在Mark Word里存储锁偏向的线程ID
- 偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可,平时只判断markword中的线程ID是否为当前ID即可,而轻量级锁每次都要CAS检查。
- 线程执行完后,也不修改markword
- 可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。
- 当调用对象hashCode()方法时会撤销对象的偏向锁状态【因为偏向锁markword中没有hashcode,换为轻量级锁后写回hashcode到markword】
那为什么轻量级锁和重量级锁可以调用hashcode呢?
因为轻量级锁hashcode会存在锁记录里,重量级锁会存在monitor里。
- 先在线程栈中创建一个锁记录(Lock Record)
锁记录就是用来记录谁持有我的锁
- 通过CAS交换markword,markword存放锁记录的地址,锁记录中存放原来的markword
CAS交换锁记录地址成功,就算加锁成功。
- 重入锁会再创建一个锁记录
轻量级锁为什么轻量
轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将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 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
轻量级锁为什么要膨胀? - 知乎
为了避免无效的自旋,不如升级为重量级锁阻塞。【重量级锁有队列,让线程去排队等着吧】
何时锁膨胀(锁膨胀的条件)
1.偏向锁膨胀:线程A持有锁,其他线程请求过锁就会膨胀,也就是说对于锁来说,只要有超过一个线程请求过锁,注意是请求过,偏向锁就会膨胀成轻量级锁。(就是来一个线程,看到markword中的线程id不是自己的线程id就升级为轻量级锁) 2.轻量级锁膨胀:线程A在运行中持有锁,线程B竞争锁,线程B会首先自旋,自旋超时之后会膨胀成重量级锁,注意条件是发生竞争。
作者:刘自在 链接:https://www.zhihu.com/question/53826114/answer/382458685 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
重量级锁【Monitor对象】
- 需要内核态切换
markword不再指向锁记录,转而指向Monitor对象。
等待线程放入EntryList中
重量级锁也有锁记录
已经加了轻量级锁,如何变为重量级锁的?
- 线程1先在markword上加了轻量级锁
- 线程2发现加轻量级锁失败,就修改markword为重量级锁【此时线程1上还认为markword时轻量级锁】
- 当线程1释放锁时,发现已经变为重量级锁了。就按照重量级锁的方式去释放,然后唤醒重量级锁的等待队列。
Monitor对象每个Object都关联一个Monitor对象
阻塞线程先进入 EntryList。
获取锁后,修改owner。指向占用对象锁的thread。
调用obj.wait()后,进入WaitSet。自适应自旋优化
疑问:自旋到底是轻量级锁状态下还是重量级锁状态下的?
但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级 不可不说的Java“锁”事 - 美团技术团队