- 锁优化
- 自适应自旋锁
- 锁消除
- 锁粗化
- 偏向锁
- 轻量级锁
- 重量级锁
为了能够在线程之间更高效地共享数据及解决竞争问题,从而提高程序的运行效率,JVM 提供了锁优化
锁优化锁优化有以下几种:
自旋锁和自适应自旋,锁消除,锁粗化, 轻量级锁,偏向锁
自旋: 如果使用阻塞线程,那么就需要挂起线程和恢复线程,如果贡献数据的锁定只持续很短的一段时间,那么为了这点时间而去挂起和恢复线程并不值得。那么就可以让后面请求锁的线程"稍等一会",但不放弃处理器,这样就是让线程执行一个 忙循环(自旋),这项技术就是自旋锁
可以参考如下代码:
ReentrantLock lock= new ReentrantLock(); while(lock.tryLock()){ } //todo 线程的具体工作
但是自旋虽然避免了线程切换的开销,但它是要占用处理器时间的,如果锁被占用的时间特别少,那么自旋的效果就会很好,但是如果自旋时间很长,那么就会浪费处理器资源。因此,自旋等待的时间一定要有一定限度。
为了解决自旋的问题,就出现了 自适应自旋,自适应意味着自旋的时间不再是固定的,而是由具体的运行情况来确定。有了自适应自旋,随着程序运行时间的增长和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越精准。
可以参考如下代码:
ReentrantLock lock= new ReentrantLock(); int count=1; while(lock.tryLock()){ count++; Thread.sleep(count*1000); } //todo 线程的具体工作锁消除
锁消除是指在某些情况下,加锁 *** 作可以完全忽略。
对象的逃逸程度 从低到高是 不逃逸、方法逃逸、线程逃逸,如果我们判断一个锁对象,是不可能线程逃逸的,那么就可以忽略所有的同步措施而直接执行。
比如如下代码:
public String contactString(String s1,String s2){ StringBuffer sb =new StringBuffer(); sb.append(s1); sb.append(s2); return sb.toString(); }
我们知道, StringBuffer 类对 append() 方法做了同步措施,使用了 synchronized修饰,也就是将 sb对象当成了锁。
经过逃逸分析,发现,sb对象的作用域被限制在该方法内,也就是说逃逸程度是 方法逃逸,其他线程是无法访问这个对象的,所以同步也是没有意义的,便会将所有同步措施忽略,就达到了 锁消除
如果一系列的连续 *** 作都对同一个对象反复加锁和解锁,那么即使没有线程竞争,频繁地进行互斥同步 *** 作也会导致不必要地性能损耗。
像上面的 append() 方法就类似这种情况,当虚拟机探测到这样一串零碎的 *** 作都对同一个对象进行加锁,就会把加锁同步的范围扩展到整个 *** 作序列之外,像上面就会在 将两个append()方法放到一个同步块里执行,这样只需要加一次锁。
偏向锁的目的是消除数据在无竞争情况下的同步原语,也就是说消除所有和同步有关系的 *** 作(包括加锁同步)
偏向锁会偏向于第一个获得它的线程,如果接下来的过程中,该锁一直没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步
如果要了解偏向锁的的具体原理,就需要先知道对象的内存结构:
可以看到,在对象头中有一个叫 Mark Word的区域,这个区域就是用来存放锁相关信息的地方。
当一个锁对象第一次被线程获取的时候,虚拟机就会将 Mark Word里的偏向模式设置为 1 ,表明这个锁对象处于偏向锁模式。同时使用CAS将 此时的线程ID记录到对象的 Mark Word里。记录之后,被记录的线程每次进入这个锁相关的代码块时,虚拟机都可以不进行任何 *** 作(如加锁、解锁)。
一旦出现非被记录的线程尝试获取这个锁,就会立刻退出偏向模式,后续会通过轻量级锁方式执行同步
轻量级锁是相对于重量级锁使用 *** 作系统互斥量来实现的传统锁来说的。
它的设计是为了在没有多线程竞争的前提下,减少传统的重量级锁使用 *** 作系统互斥量产生的性能消耗.
具体原理:
在代码即将进入同步块的时候,如果还没有其他线程获取锁对象,那么就会在当前线程的 栈帧中建立一个名为 Lock Record的空间,来存储锁对象现在的 Mark Word的拷贝,然后虚拟机将使用CAS *** 作尝试把对象的Mark Word更新为 指向 拷贝的指针。如果这个 *** 作成功,那么就代表这个线程拥有了这个对象的锁,并且将锁对象的锁状态标志位设置成轻量级 ,
如果这个更新失败了,那么就说明至少存在一条线程和当前线程竞争该对象,虚拟机首先会检查线程该对象 Mark Word是否指向当前线程的栈帧,如果是,那么就直接进入同步块,如果不是,那么轻量级锁就不再有效,会膨胀为 重量级锁 ,此时 Mark Word存储的就是指向 重量级锁(互斥量) 的指针,后面等待锁的线程就必须阻塞。
解锁的时候,如果当前还是轻量级,那么就直接把栈帧里的 拷贝直接替换回去就行了。如果当前是重量级就需要唤醒其他线程
如果没有线程竞争,轻量级锁就通过CAS *** 作成功地避免了使用互斥量的开销,但如果存在线程竞争,那么除了互斥量本身的开销之外,还额外发生了Cas,此时会比重量级锁慢
重量级锁就是直接用 *** 作系统提供的 互斥量 实现线程的互斥
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)