作者:bravo1988
链接:https://www.zhihu.com/question/317687988/answer/1715863550
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
所谓JVM锁,其实指的是诸如synchronized关键字或者ReentrantLock实现的锁。之所以统称为JVM锁,是因为我们的项目其实都是跑在JVM上的。理论上每一个项目启动后,就对应一片JVM内存,后续运行时数据的生离死别都是在这一片土地上。
什么是锁、怎么锁?
明白了“JVM锁”名字的由来,我们再来聊什么是“锁”,以及怎么“锁”。
有时候我们很难阐述清楚某个事物是什么,但很容易解释它能干什么,JVM锁也是这个道理。JVM锁的出现,就是为了解决线程安全问题。所谓线程安全问题,可以简单地理解为数据不一致(与预期不一致)。
什么时候可能出现线程安全问题呢?
当同时满足以下三个条件时,才可能引发线程安全问题:
- 多线程环境
- 有共享数据
- 有多条语句操作共享数据/单条语句本身非原子操作(比如i++虽然是单条语句,但并非原子操作)
比如线程A、B同时对int count进行+1操作(初始值假设为1),在一定的概率下两次操作最终结果可能为2,而不是3。
那么加锁为什么能解决这个问题呢?
如果不考虑原子性、内存屏障等晦涩的名词,加锁之所以能保证线程安全,核心就是“互斥”。所谓互斥,就是字面意思上的互相排斥。这里的“互相”是指谁呢?就是多线程之间!
怎么实现多线程之间的互斥呢?
引入“中间人”即可。
注意,这是个非常简单且伟大的思想。在编程世界中,通过引入“中介”最终解决问题的案例不胜枚举,包括但不限于Spring、MQ。在码农之间,甚至流传着一句话:没有什么问题是引入中间层解决不了的。
而JVM锁其实就是线程和线程彼此的“中间人”,多个线程在操作加锁数据前都必须去问问“中间人”它是否允许当前线程操作这个数据:
锁在这里扮演的角色其实就是守门员,是唯一的访问入口,所有的线程都要经过它的拷问。在JDK中,锁的实现机制最常见的就是两种,分别是两个派系:
- synchronized关键字
- CAS+AQS
个人觉得synchronized关键字要比CAS+AQS难理解,但CAS+AQS的源码比较抽象。这里简要介绍一下Java对象内存结构和synchronized关键字的实现原理。
Java对象内存结构
要了解synchronized关键字,首先要知道Java对象的内存结构。强调一遍,是Java对象的内存结构。
它的存在仿佛向我们抛出一个疑问:如果有机会解剖一个Java对象,我们能看到什么?
右上图画了两个对象,只看其中一个即可。我们可以观察到,Java对象内存结构大致分为几块:
- Mark Word(锁相关)
- 元数据指针(class pointer,指向当前实例所属的类)
- 实例数据(instance data,我们平常看到的仅仅是这一块)
- 对齐(padding,和内存对齐有关)
如果此前没有了解过Java对象的内存结构,你可能会感到吃惊:天呐,我还以为Java对象就只有属性和方法!
是的,我们最熟悉实例数据这一块,而且以为只有这一块。也正是这个观念的限制,导致一部分初学者很难理解synchronized。比如初学者经常会疑惑:
- 为什么任何对象都可以作为锁?
- Object对象锁和类锁有什么区别?
- synchronized修饰的普通方法使用的锁是什么?
- synchronized修饰的静态方法使用的锁是什么?
这一切的一切,其实都可以在Java对象内存结构中的Mark Word找到答案:
很多同学可能是第一次看到这幅图,会感到有点懵,没关系,我也很头大,都一样的。
Mark Word包含的信息还是蛮多的,但这里我们只需要简单地把它理解为记录锁信息的标记即可。上图展示的是32位虚拟机下的Java对象内存,如果你仔细数一数,会发现全部bit加起来刚好是32位。64位虚拟机下的结构大同小异,就不特别介绍。
Mark Word从有限的32bit中划分出2bit,专门用作锁标志位,通俗地讲就是标记当前锁的状态。
正因为每个Java对象都有Mark Word,而Mark Word能标记锁状态(把自己当做锁),所以Java中任意对象都可以作为synchronized的锁:
synchronized(person){
}
synchronized(student){
}
所谓的this锁就是当前对象,而Class锁就是当前对象所属类的Class对象,本质也是Java对象。synchronized修饰的普通方法底层使用当前对象作为锁,synchronized修饰的静态方法底层使用Class对象作为锁。
但如果要保证多个线程互斥,最基本的条件是它们使用同一把锁
作者:bravo1988
链接:https://www.zhihu.com/question/317687988/answer/1715863550
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
对同一份数据加两把不同的锁是没有意义的,实际开发时应该注意避免下面的写法:
synchronized(Person.class){
// 操作count
}
synchronized(person){
// 操作count
}
或者
public synchronized void method1(){
// 操作count
}
public static synchronized void method1(){
// 操作count
}
需要更多教程,微信扫码即可
别忘了扫码领资料哦【高清Java学习路线图】
和【全套学习视频及配套资料】



