1 Java中锁的概念
独享锁:写锁,获得锁的线程可以修改资源,其它线程不能加锁。
共享锁:读锁,获得锁的线程只读不写,其它线程可以加读锁,不能加写锁。
乐观锁:假定没有发生冲突,如果获取的数据与之前的数据不一致,则读取最新数据;乐观锁一般会采用版本号机制或CAS算法实现。
悲观锁:假定会发生冲突,一开始就不信任对方,从读数据开始就上锁,所有对数据的操作都进行同步。Synchronized和ReentrantLock就是典型的悲观锁。
可重入锁:线程获得锁后,可以进入同一把锁同步的其它代码。
自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程就循环等待,不断请求锁直至锁被释放获得锁,才会退出循环,CAS就是典型的自旋锁,自旋会有次数限制,超过次数就升级锁。
2 同步关键字Synchronized
- 用于实例方法和代码块,锁的是实例对象
- 用于静态方法,锁的是Class类对象
- 锁的作用域:对象锁、类锁、分布式锁,Synchronized只能作为对象锁或类锁
根据JVM规范描述的实现原理,代码块同步是使用monitorenter和monitorexit指令实现的,方法同步同样可以通过这两个指令实现。
synchronized(this){
i++;
}
我们经常可以看到类似上面这样的代码,这时synchronized是作为对象锁,锁住的是java对象,那么请思考下面几个问题
- 加锁的状态如何记录?
- 锁状态会被保存到java对象中吗?
- jvm是如何做到根据锁状态来进行线程挂起和唤醒的?
假装思考了几十秒后,下面开始揭晓答案
- 对象头
- Mark Word:加锁状态会被记录在这块内存区域
- Class Meta Address:指向方法区中存放该对象类信息的地址
- Array Length:如果是数组对象,则会记录数组长度
- 成员变量
- 填充字节,视32位或64位系统而定
下面具体讲解对象头中的Mark Word
锁标志位(2bit) | 锁状态 | |
---|---|---|
对象哈希码、分代年龄(4bit)、是否偏向锁(1bit) | 01 | 无锁 |
偏向的线程ID、分代年龄(4bit)、是否偏向锁(1bit) | 01 | 偏向锁 |
指向虚拟机栈中锁记录的指针 | 00 | 轻量级锁 |
Monitor监视器地址 | 10 | 重量级锁 |
空 | 11 | GC标记 |
3 偏向锁及锁的升级过程
偏向锁:HotSpot作者研究发现,在大多数情况下是不存在锁竞争的,常常是一个线程多次获得同一把锁,因此为了降低获得锁的成本,引入了偏向锁;以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁。
在JDK6以后,默认已经开启了偏向锁这个优化,通过JVM参数-XX:-UseBiasedLocking
来禁用偏向锁
轻量级锁:竞争锁的线程不会挂起阻塞,而是进入自旋(CPU空转消耗CPU资源),自旋一定次数,就会升级锁。
重量级锁:多个线程争抢锁,一个线程获得锁,其它线程挂起阻塞。
参考文章
大白话聊聊Java并发面试问题之Java 8如何优化CAS性能?【石杉的架构笔记】
Java锁—偏向锁、轻量级锁、自旋锁、重量级锁
网易云课堂《Java高级开发工程师》
结语
本人所有博客仅用于学习记录,不做任何商业用途,如涉及侵权,还请联系删除,感谢阅读,欢迎留言,一起进步~