随着互联网的发展,越来越多的互联网企业面临着用户数量增加带来的并发安全问题。 本文重点介绍了java并发中几种常见的锁定机制。
1 .偏转锁定
偏转锁定是JDK1.6提出的锁定优化机制。 其核心思想是,如果程序没有冲突,则取消对以前获取锁定的线程的同步操作。 也就是说,一旦线程获取了一个锁定,就进入偏转模式,并且当线程再次请求该锁定时,不再需要执行相关联的同步操作,从而节省了操作时间,并且在此期间如果另一个线程请求锁定,则退出偏转模式。 在JVM上使用-XX: UseBiasedLocking
package JVM项目;
import java.util.List;
import java.util.Vector;
公共类基本{
publicstaticlistnumberlist=new vector (;
publicstaticvoidmain (字符串[ ] args ) {
long begin=system.current time millis (;
int count=0;
int startnum=0;
wile (计数1000000 ) {
Numberlist.add(Startnum;
startnum=2;
出局;
}
longend=system.current time millis (;
system.out.println(end-begin;
}
}
初始化向量,向其中添加10000000个Integer对象,并输出时间差。 现在测试偏转锁定的性能。 为什么要用Vector而不是ArrayList呢?
因为ArrayList不是线程安全的,而Vector是线程安全的。 这样说可能还不具体。 看看源代码吧。
在Vector中的大多数操作都是同步的,但在ArrayList中没有,因此Vector是线程安全的。
接下来,测试一下打开还是不打开偏转锁定对程序的性能有多大的影响。
如下设定JVM启动(打开偏转锁定)参数。
如下设定JVM启动(关闭偏转锁定)参数。
完美! 打开偏转锁定的程序运行时间明显较短,与不打开偏转锁定相比,在一个线程中处理一个对象的同步方法具有一定的优点。 实际上,如果只有一个线程处理具有同步方法的Vector对象,则也可以理解为此时对Vector的操作将变为对ArrayList的操作。
偏转锁定在锁定竞争激烈的情况下没有什么优化效果。 因为大量竞争导致具有锁定的线程不断切换,从而使锁定难以保持在偏转模式。 在这种情况下,使用偏转锁定不仅无法优化性能,反而可能会降低系统的性能。 因此,在激烈竞争的情况下,请尝试使用
-XX:-UseBiastedLocking参数禁用偏转锁定。
2 .轻型锁
如果偏转锁定失败,Java虚拟机将要求线程申请轻量级锁定,轻量级锁定在虚拟机内部使用BasicObjectLock对象实现,该对象由BasicLock对象和保存该对象的Java对象指针组成BasicObjectLock对象放置在Java堆栈框架中。 BasicLock对象内部还保留了一个displaced_header字段,用于备份对象头大的硬币Word。
如果线程具有对象锁定,则对象头大的硬币Word的信息如下
[ptr|00]锁定
末尾的2位是00,眼睛整体大的硬币Word是指向基本锁定对象的指针。 因为BasicObjectLock对象位于线程堆栈中,所以指针始终指向具有锁定的线程堆栈区域。 如果需要确定一个线程是否拥有该对象,则只需确定对象头部的指针是否位于当前线程的堆栈地址范围内即可。 此外,BasicLock对象的displaced_header备份原始对象的大眼睛硬币word的内容,而BasicObjectLock对象的obj字段指向具有锁定的对象的头部
3 .重量级锁
如果轻量级锁定失败,虚拟机将使用重量锁定。 使用重量锁定时,对象眼睛大的硬币Word如下所示。
[ptr|10]莫妮卡
在操作过程中,重锁可能会在操作系统级别挂起线程,这将大大增加线程之间的切换和调用成本。
4 .旋转锁定
在线程未获取锁定的情况下,旋转锁定可以运行空循环,而无需挂起(旋转是指自己执行空循环)。在一些空循环之后,如果线程可以获取锁定,则旋转锁定执行如果线程无法获取锁定,它将挂起。
使用旋转锁定,线程锁定的概率相对减少,线程执行的一致性相对增强。 因此,对于锁定竞争不那么激烈、锁定占用时间短的并发线程,具有一定的积极意义,但对于锁定竞争激烈、单线程锁定占用较长时间的并发程序,在等待旋转后,会坚决应对
在JDK1.6中,Java虚拟机提供打开旋转锁定的-XX: UseSpinning参数,并使用-XX:PreBlockSpin参数设置旋转锁定的等待次数。
从JDK1.7开始,自旋锁参数已被取消,虚拟机不再支持用户设置自旋锁。 旋转锁定始终运行,旋转锁定的次数也由虚拟机自动调整。