目录
synchronized 是什么
用在多线程领域,使线程同步,synchronized(class) 加一个对象就可以上锁
这样就可以避免一些线程安全问题(访问共享资源,读写共享资源,临界区)
使用方法:
用代码块或者加在方法上。
在Java早期版本中, synchronized属于重量级锁,效率低下,因为监视器锁( monitor)是依赖于底层的操作系统的 Mutex Lock来实现的,Java的线程是映射到操作系统的原生线程之上的。
如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized效率低的原因。
庆幸的是在Java6之后Java官方对从JVM层面对 synchronized较大优化,所以现在的 synchronized锁效率也优化得很不错了。
java1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
monitor原理
被翻译为
监视器
或
管程
Java
对象都可以关联一个
Monitor
对象,如果使用
synchronized
给对象上锁(重量级)之后,该对象头的Mark Word
中就被设置指向
Monitor
对象的指针。
各种锁
重量级锁
和monitor相关联属于重量级锁,因为有和操作系统的交互,会占用较多资源,所以有了轻量级锁和偏向锁。三者可以在对象的markword字段中后三位二进制数得到,01是正常状态和偏向锁状态,00是轻量级锁,10是重量级锁。
自旋锁(循环上锁)
重量级锁会有阻塞,阻塞再唤醒就会有线程的上下文切换,效率较低。
于是在一个线程上锁时发现对象已经被占用,这时不会立马进入阻塞队列,而是会连续几次进行上锁的尝试。
这样会减少一些线程的阻塞,提升效率。自旋锁在单核cpu下没有意义,反而降低程序效率。
轻量级锁的实现
如果没有锁膨胀,轻量级锁没有和操作系统的交互。
创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word 。
在进行锁定时,通过CAS(compare and swap)和该对象头中的mark word进行比较交换,如果成功了,那么此时 锁记录中就会存放对象头的mark word( hashcode epoch age等数据),对象的mark word就会被替换为一个64位的二进制数(末尾是00 表明是轻量级锁),表示该对象被某个线程锁了。
如果CAS不成功,就会进入锁重入或者锁膨胀(升级为重量级锁)
上锁过程,如果该线程的子方法里面还要加锁,那么就会发生锁重入,即再在前面的栈帧上方建立新的栈帧,里面同样维护着一个lock record内存,引用的对象依然时同一个,不过存放mark word 的地方存放null,因为不能进行CAS了。
锁膨胀就是当另一个线程想要CAS一个对象时,发现该对象已经被上锁,那么这时就会进入锁膨胀:
为该对象申请一个 monitor对象,然后第一个线程的栈帧中锁记录中的的object的地址就不是刚才的轻量级锁的标识了,就会指向monitor的地址,表明自己是一个重量级锁锁定的对象,同时后一个线程进入 entrylist,阻塞。
锁的释放
线程释放锁的时候如果CAS成功,那么说明没有发生锁膨胀,正常CAS设置锁对象的mark word即可。
线程释放锁的时候,CAS失败,就会进入重量级锁的解锁流程,即找到monitor对象,把owner设置为null,唤醒entrylist中的blocked线程。
这样的话就比重量级锁的开销更小,效率更高。
偏向锁
进一步优化轻量级锁(CAS导致效率变低),使用线程id来替换markword
而不是采用锁记录CAS markword。
每次只需要检查id是不是一个线程的即可。
wait notify notifyAll
参考:
黑马 JUC
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/92799.html