计算机系统应用教程网站

网站首页 > 技术文章 正文

【面试】谈谈Java锁机制 java锁的作用

btikc 2024-10-08 01:17:35 技术文章 8 ℃ 0 评论

在被问到并发相关问题的时候,你就要注意面试官是否想要将你引入锁的相关知识范围。相信你已经听过“乐观锁”,“悲观锁”等等,本文将分类介绍Java几种锁。




悲观锁(Pessimistic Lock)

在每次访问数据的时候,全都认为这次访问是风险的,“一刀切”的进行上锁操作。在数据库中往往就用到了很多悲观锁,行锁、表锁,读写锁等等都是先锁再操作。有一个前提,必须保证外部因素不会影响系统数据,只有这样,悲观锁这种强烈的排他性和独占性才行之有效。

synchronized是Java中的关键字,是一种同步锁,也是一种悲观锁。

synchronized可以修饰实例方法,静态方法,代码块。synchronized会导致其它的线程挂起等待锁释放synchronized的底层是通过一个monitor的对象来完成,wait/notify等方法也是依赖操作monitor对象来实现的。

为什么说Synchronized会对性能有较大影响?JVM通过调用操作系统的互斥原语mutex来实现Synchronized,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,这样一来性能必将会有较大影响。

在HotSpot JVM中,锁有个专门的名字:对象监视器(Object Monitor)。JVM保证每个MonitorEnter必须有对应的MonitorExit。

monitorenter:每个对象都是一个监视器锁(monitor)。在尝试获取对象锁的的时候,其实就是在获取monitor的所有权。当monitor的计数为0的时候,这表示当前还未有任何线程获取了monitor的所有权,把monitor计数加1,该线程就获取了monitor的所有权。如果当前线程已经拥有了monitor的所有权,则把monitor计数加1。如果其它线程拥有了monitor的所有权,则当前线程则会阻塞直到monitor计数为0的那一刻,然后根据相关策略来试图获取monitor的所有权。

monitorexit:如果monitor的持有线程执行了该命令,则尝试把monitor计数减1。如果monitor的计数为零,则完全退出monitor所有权,此时monitor就可以被其它线程竞争拥有了。

除了上述两条指令,Java编译过程中ACC_SYNCHRONIZED这条指令也将操作monitor对象。

在JVM中对象在内存存储分为三个部分,对象头、实例数据,对齐填充。在Object Head中Runtime MarkWord中存储了 锁状态标志,持有锁线程信息,偏向ID等。01是初始状态,未加锁;偏向ID则存储的是占有该对象的线程ID;还有可能存储线程的栈中锁记录指针信息(Lock Record)。当MarkWord锁标识位为10,其中指针指向的是Monitor对象的起始地址。

当线程进入该对象发现标志位01未加锁,将对象的MarkWord复制到自己的栈(Lock Record)中。Lock Record中有一个Owner字段存放拥有该锁的线程的唯一标识(偏向ID)(或者object mark word),表示该线程拥有这个对象锁。


乐观锁(Optimistic Locking)

乐观锁是什么呢?每次不加锁而是假设没有冲突而去完成某项操作,如果失败就重试,直到成功为止。乐观锁用到的机制就是CAS(Compare and Swap)。CAS就是将记录原值与内存位置比较,如果相符就把预期新值更新到内存位置,如果不相符则不做任何操作。

如何选择合适的场景使用乐观锁?乐观锁采取CAS的策略,这样循环等待尝试的策略在某些极端情况下更新失败的概率会非常大,而且很容易造成业务失败。

相比之下,悲观锁更新失败概率小,但是依赖数据库锁会造成效率低下的问题。

当下互联网架构,高并发高可用高性能的要求下,配合业务设计,乐观锁能够很好的发挥它的优势。



锁优化

锁的四种状态,无锁状态、偏向锁状态、轻量级锁状态,重量级锁状态

锁的升级是单向的,只能从低到高升级,不会出现降级的现象。

  • 自旋锁

上文提到mutex操作会消耗大量资源,因为线程的频繁切换。自旋锁在对于锁时间短的场景下非常好用。如果一个线程发现资源已经被锁,它将自旋等待(循环查询锁是否释放),而不是进入阻塞挂起状态,这样就可以避免线程切换带来的系统资源开销。值得注意的是,自旋操作在面对锁等待时间较长的场景下就会适得其反,将一直占用CPU资源空转。JDK1.6以后,自旋锁在面对自旋操作成功率大的锁增加了自旋等待次数,成功率小的就减少自旋次数或者升级锁。

  • 锁消除

在某些情况下,JVM检测到不可能发生资源竞争,则JVM会在编译过程中将锁执行消除优化。

  • 锁偏向

如果一直线程多次反复的获取同一个锁,其中的CAS等获取锁的操作就可以考虑优化,节省系统资源开销。偏向锁,就是在一个线程获取锁后,后续该线程再次进入时避免CAS操作,提升同步块的执行性能。

  • 锁粗化

如果反复不断的进入到某几段锁操作逻辑中,可以将几段锁操作合并。通过合并操作,形成一个更大范围的锁,这就是锁粗化。

  • 轻量级锁与重量级锁

轻量级锁是在偏向锁失效后,在没有多线程竞争的前提下,通过CAS等操作减少使用操作系统互斥量产生的性能消耗,由无锁或者偏向锁状态膨胀而来的一种锁。

依赖于操作系统Mutex实现的锁为重量级锁,比如synchronized。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表