大家好,我是程序视点的小二哥!今天我们来聊聊Java中的锁!
带着上面疑问,我们一起来解“锁”疑惑
!以上问题会分成几篇文章来讲,方便大家记忆!欢迎持续关注【程序视点】,这样就不会错过之后的精彩内容啦!
每一个刚接触多线程并发编程的同学,当被问到,如果多个线程同时访问一段代码,发生并发的时候,应该怎么处理?
我相信闪现在脑海中的第一个解决方案就是用synchronized,让这段代码同一时间只能被一个线程执行。
我们也知道,synchronized关键字可以用在方法上,也可以用在代码块上,如果要使用synchronized,我们一般就会如下使用:
public synchronized void doSomething() { //do something here}
或者
synchronized(LockObject) { //do something here}
那么实际上,synchronized关键字到底是怎么加锁的?锁又长什么样子的呢?
关于锁,还有一些什么样的概念需要我们去认识,去学习,去理解的呢?
以前在学习synchronized的时候,就有文章说, synchronized是一个很重的操作,开销很大,不要轻易使用,我们接受了这样的观点,但是为什么说是重的操作呢,为什么开销就大呢?
到java1.6之后,java的开发人员又针对锁机制实现了一些优化,又有文章告诉我们现在经过优化后,使用synchronized并没有什么太大的问题了,那这又是因为什么原因呢?到底是做了什么优化?
那今天我们就尝试着从锁机制实现的角度,来讲述一下synchronized在java虚拟机上面的适应场景是怎么样的。
由于java在1.6之后,引入了一些优化的方案,所以我们讲述synchronized,也会基于java1.6之后的版本。
首先,我们要知道锁其实就是一个对象,java中每一个对象都能够作为锁。
所以我们在使用synchronized的时候,
我们知道,所谓的对象,无非也就是内存上的一段地址,上面存放着对应的数据,那么我们就要想,作为锁,它跟其它的对象有什么不一样呢?怎么知道这个对象就是锁呢?怎么知道它跟哪个线程关联呢?它又怎么能够控制线程对于同步代码块的访问呢?
可以了解到在虚拟机中,对象在内存中的存储分为三部分:
其中,对象头填充的是该对象的一些运行时数据,虚拟机一般用2到3个字宽来存储对象头。
其结构简单如下:
长度 | 内容 | 说明 |
---|---|---|
32/64bit | Markword | hashCode,GC分代年龄,锁信息 |
32/64bit | Class Metadata Address | 指向对象类型数据的指针 |
32/64bit | Array Length | 数组的长度(当对象为数组时) |
从上表中,我们可以看到,锁相关的信息,是存在称之为Markword中的内存域中。
拿以下的代码作为例子,
synchonized(LockObject) { //do something here}
在对象LockObject的对象头中,当其被创建的时候,其Markword的结构如下:
bit fields | 分代年龄 | 是否偏向锁 | 锁标志位 |
---|---|---|---|
hash | age | 0 | 01 |
从上面Markword的结构中,可以看出
所有新创建的对象,都是可偏向的(锁标志位为01),但都是未偏向的(是否偏向锁标志位为0)
当线程执行到临界区(critical section)时,此时会利用CAS(Compare and Swap)操作,将线程ID插入到Markword中,同时修改偏向锁的标志位。
这说明此对象就要被当做一个锁来使用,那么其Markword的内容就要发生变化了。
其结构其会变成如下:
bit fields | epoch | 分代年龄 | 是否偏向锁 | 锁标志位 |
---|---|---|---|---|
threadId | epoch | age | 1 | 01 |
可以看到,
也就是说,这个锁将自己偏向了当前线程,心里默默地藏着线程id, 在这里,我们就引入了“偏向锁”的概念。
在此线程之后的执行过程中,如果再次进入或者退出同一段同步块代码,并不再需要去进行加锁或者解锁操作,而是会做以下的步骤:
以下是Java开发人员提供的一张图:
“偏向锁”是Java在1.6引入的一种优化机制,其核心思想在于,可以让同一个线程一直拥有同一个锁,直到出现竞争,才去释放锁。
因为经过虚拟机开发人员的调查研究,在大多数情况下,总是同一个线程去访问同步块代码,基于这样一个假设,引入了偏向锁,只需要用一个CAS操作和简单地判断比较,就可以让一个线程持续地拥有一个锁。
也正因为此假设,在Jdk1.6中,偏向锁的开关是默认开启的,适用于只有一个线程访问同步块的场景。
在上面,我们说到,一旦出现竞争,也即有另外一个线程也要来访问这一段代码,偏向锁就不适用于这种场景了。
如何解决的呢?下篇文章将带大家了解锁膨胀、锁撤销、轻量级锁等内容!持续关注,这样就不会错过之后的精彩内容啦!
如果这篇文章对你有帮助的话,别忘了【点赞】【分享】支持下哦~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。