Java Lock
为什么有synchronized?
c++中并没有这种锁住代码块的,而且不需要unlock的操作,虽然有各种Guard锁,但是只是利用了RAII来进行锁的释放。
最主要原因可能是应用简单:
- 不需要unlock,也就不需要考虑各种异常情况,会降低死锁的可能性
- 能够在方法前加上这关键字,该方法就是同步的了,很多类库如Vector、StringBuffer都是这样干的
简单的同时,也省略了一些特性,例如与ReentranLock比:
- 不是公平的(实际情况中大多场景不要求严格公平,维护公平对性能有消耗)
- 不能被中断(实际应用被中断的场景也不多)
- 不支持中断、超时、尝试获取锁
为什么在使用条件变量的时候,需要while(条件不满足)而不是if(条件不满足)?
- 在操作系统,是因为会有“虚假唤醒”,线程被错误唤醒,唤醒的时候条件可能还没满足,所以需要再次检查一下条件。
- 在java中,最低也是要有个if(条件不满足)的判断,因为线程被唤醒的时候,并不是无缝切换,而只是从条件队列移动到了同步队列的后面,虽然当时移动的时候是满足的,但是在同步队列中排队后,可能条件就不满足了。
重量级锁
内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。
之所以重,是因为每次锁都是直接使用系统调用,导致内核态与用户态切换、甚至线程的阻塞,然后线程切换。
以下,所有的自旋锁、轻量级锁、偏向锁,都是为了,不进行内核态与用户态切换、不进行线程切换。
轻量级锁
与重量级锁类似,在Mark word存锁的地址,在从偏向锁升级重量级锁前,会有自旋锁的操作。
偏向锁
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。
这是偏向锁设计的初衷。这样,在线程进入同步块的时候,不需要进行CAS操作进行加锁和解锁,只需要测试当前线程的Mark Word是否存在指向当前线程的偏向锁,在无竞争的时候几乎没有任何的消耗。
默认的JVM是会开启偏向锁的特性的。
锁膨胀
- 在jvm没有显示关闭偏向锁的情况下,初始状态时默认是偏向锁时,线程请求先通过CAS替换mark word中threadId,如果替换成功则该线程持有当前锁。如果替换失败,锁会升级为轻量级锁,
- 线程请求会尝试CAS替换mark word中指向栈中锁记录的指针,如果替换成功则该线程持有当前锁。 如果替换失败,当前线程会自旋一定次数,继续尝试获取CAS替换,如果超过一定自旋次数,锁升级为重量级锁。