首页 > 数据库 > 数据库开发技术 > 锁升级带来的对象变化
前言
|锁的必要性

|锁的升级

public class SynchronizedTest {
private static Object object = new Object();
/**打印出被锁的对象*/
private static void print() throws InterruptedException {
synchronized (object) {
System.out.println("####################################");
Thread.sleep(1000);//为了加剧线程间的竞争
System.out.println(ClassLayout.parseInstance(object).toPrintable());//打印对象被锁之后布局
}
}
public static void main(String[] args) throws InterruptedException {
//调用synchronized之前
System.out.println(ClassLayout.parseInstance(object).toPrintable());//打印对象锁之前的布局
//调用synchronized之后,单个线程没有竞争
print();
//有若干个线程竞争情况下
for (int i = 0; i < Integer.parseInt(args[0]); ++i){
new Thread(() -> {
try {
print();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}

锁标志 |
||
0 |
01 |
|
1 |
01 |
|
- |
00 |
|
- |
10 |
|
- |
11 |
所谓偏向锁?就是该锁偏向某一个线程,那什么情况下会偏向呢?就是只有一个线程对单个资源进行操作时,此时没有其他线程跟你竞争,那么此时,这个锁是偏向你的,即只被你使用。
类比到做核酸就是,刚好只有你一个人,那还排什么队啊,直接去护士小姐姐面前坐下就好了,此时的小姐姐只归你一人所有。
这段代码展示了一个对象从没有被加锁,到加锁后的状态(结果按理说应该是从无锁状态,升级到偏向锁状态),看下运行结果:

为了方便演示出偏向锁的效果,这里需要借助参数-XX:BiasedLockingStartupDelay,意思是对象的偏向锁启动延迟,JDK默认是4s,这里为了方便演示出效果,改为0,即不等待。
发现没?这个对象无论是在加锁前还是加锁后,其锁标记位都是101,即都是偏向锁,跟我们的预期并不符(因为偏向锁的延迟设置为0导致的)。
而当我把偏向锁延迟参数改为100ms时,再看一下运行效果:

可以看到,对象在加锁前打印出来为[无锁]状态,而加锁之后,瞬间就升级到了[轻量级锁]状态。
因此可以证明,锁的升级过程并不是严格按照:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁,这种顺序来的。
2. 轻量级锁
也叫自旋锁——CAS(compare and swap),从字面翻译叫做:比较并交换。
它是一种乐观锁的实现,所谓乐观锁,就是[总以为自己随时可以],比如在排队做核酸的时候,别人都在低头玩手机等,而你却总是盯着最前面看,总以为下一个就到自己了,所以你一直都不闲着。当排队人数比较少时候,用这招比较管用,效率很高。
之所以叫轻量级锁,是因为这部分是由JVM自己来管理。
为了演示出轻量级锁的效果,我们增加1个额外的线程参与竞争,来看看运行效果:

可以看到,对象的锁状态位由101变成了00,即偏向锁升级成了轻量级锁。
轻量级锁只有在线程竞争不激烈的情况下启用,那么什么时候变成重量级锁呢?
3. 重量级锁
之所以叫重量级,是因为这个时候排队的线程不再傻傻的一直盯着被占用的资源看有没有被释放,因为这个时候如果竞争的线程太多,你这样一直盯着太费眼了(浪费CPU资源)。
于是,这个时候就想出来一个比较节省资源的办法——进入等待队列,而这个队列是由操作系统来维护的,因此,这里面有个用户态向内核态切换的过程,这也是为什么叫重量级锁的原因,你都惊动了操作系统老大了,能不重吗?
这里面其实有个悖论,那就是锁之所以从轻量级升级到重量级,目的在于节省资源,但是重量级锁又是我们工作中要尽可能避免的,只是这个避免不是要我们不要用重量级锁,而是通过合理的程序设计和合理的资源分配,来尽量减少线程间的竞争。
因为当前我使用的是JDK1.8,想要实现轻量级锁到重量级锁的升级,加大竞争的线程数就够了。
JDK之前版本默认是单个线程循环10次后如果还没有获得锁,那么从轻量级锁升级为重量级锁,或者等待的线程数超过CPU核数的一半即升级。
但是JDK1.6之后,改成了自适应自旋,也就是JVM自己根据当前运行环境来判断什么时候将轻量级锁升级为重量级锁
来,通过增加竞争的线程数量,我们来看一下效果:

当我把竞争的线程数量加到3之后,发现对象的锁状态位由101变成了10,即偏向锁升级成了重量级锁。
结语
——

原文作者:安瑞哥是码农;如有侵权,请联系管理员删除。