Synchronized用法
Synchronized块
对于同步代码块,编译成字节码时,会在同步代码块的前后加上 monitorenter 和 monitorexit
Synchronized方法
对于同步方法,编译成字节码时,会给方法加上ACC_SYNCHRONIZED,当调用改方法时,会先尝试获取锁
Synchronized原理
monitor实现原理
Monitor对象
图解
Contention List:所有请求锁的线程将被首先放置到该竞争队列<br>
Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List<br>
Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set<br>
OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck<br>
Owner:获得锁的线程称为Owner<br>
Monitor对象临界资源对象一起创建,销毁
偏向锁
锁争夺也就是对象头指向的Monitor对象的争夺,一旦有线程持有了这个对象,标志位修改为1,就进入偏向模式,同时会把这个线程的ID记录在对象的Mark Word中
这个过程是采用了CAS乐观锁操作的,每次同一线程进入,虚拟机就不进行任何同步的操作了,对标志位+1就好了,<br>不同线程过来,CAS会失败,也就意味着获取锁失败。<br>
应用场景
偏向锁主要用来优化同一线程多次申请同一个锁的竞争
用于解决同一个线程反问执行同步代码时,重量级锁存在反问获取Monitor对象带来的不必要的性能开销
当对象被当做同步锁并有一个线程抢到了锁时,锁标志位还是 01,“是否偏向锁”标<br>志位设置为 1,并且记录抢到锁的线程 ID,表示进入偏向锁状态。
在高并发场景下,当大量线程同时竞争同一个锁资源时,偏向锁就会被撤销,发生<br>stop the word 后
轻量级锁
应用场景
轻量级锁适用于线程交替执行同步块的场景,绝大部分的锁在整个同步周期内都不存在长时<br>间的竞争。
当有另外一个线程竞争获取这个锁时,由于该锁已经是偏向锁,当发现对象头 Mark Word<br>中的线程 ID 不是自己的线程 ID,就会进行 CAS 操作获取锁,如果获取成功,直接替换<br>Mark Word 中的线程 ID 为自己的 ID,该锁会保持偏向锁状态;如果获取锁失败,代表当<br>前锁有一定的竞争,偏向锁将升级为轻量级锁。
自旋锁
JVM 提供了一种自旋锁,可以通过自旋方式不断尝试获取锁,从而避免线程被挂起阻塞。<br>这是基于大多数情况下,线程持有锁的时间都不会太长,毕竟线程被挂起阻塞可能会得不偿失。<br>
自旋锁重试之后如果抢锁依然失败,同步锁就会升级至重量级锁
重量级锁(传统锁)
依赖于系统的同步函数,在linux上使用mutex互斥锁,最底层实现依赖于futex
这些同步函数都涉及到用户态和内核态的切换、进程的上下文切换,成本较高
对于加了synchronized关键字但运行时并没有多线程竞争,或两个线程接近于交替执行的情况,使用传统锁机制无疑效率是会比较低的
动态编译实现锁消除 / 锁粗化<br>
Java 还使用了编译器对锁进行优化。JIT 编译器在动态编译同步块的时<br>候,借助了一种被称为逃逸分析的技术,来判断同步块使用的锁对象是否只能够被一个线程<br>访问,而没有被发布到其它线程
减小锁粒度<br>
我们可以考虑将一个数组和队列对象拆成多个小对象,来降低锁竞争,提升并行<br>度。