郑州市众泰网络科技有限公司
首页 | 联系方式 | 加入收藏 | 设为首页 | 手机站

产品目录

联系方式

联系人:业务部
电话: 00128-
邮箱:service@tjzhizhong.com

当前位置:首页 >> 新闻中心 >> 正文

Synchronized学习笔记(2)-三种锁状态

字号:
摘要:Synchronized学习笔记(2)-三种锁状态

对象头:

介绍三种锁状态之前先介绍一下对象头:

大家应该都知道网络中的各种报文吧:IP报文、TCP报文、UDP报文,报文中的头部是不是都会有一些bit位来表示一些信息,比如TCP报文可以表示源端口、目的端口、SYN、ACK、SEQ、窗口大小等等(详细请百度TCP报文结构),这些都是在发送数据之前的信息,用来标识一些必要的信息。

相同的,对象在JVM存储的时候也有类似于报文头部的东西,叫做对象头,里面保存了一些基本的信息。根据JVM位数不同,有不同的长度,为了简单,我们以32位的情况来看

对象头有32bit的mark word 32bit的class meta address和32bit的array length(是数组的时候)。其中的mark word就是我们要重点关注的。32位时mark word的结构如下:


要注意,这个markword是复用的结构,根据不同的锁状态,markword存的值是不同的。我们可以清楚的看到,最后3个bit的就可以指导目前对象的锁的状态。下面我们来看一下markword里面的这三种锁。


我们都知道在JDK5中出现了Concurrent包,里面的lock锁性能比它好,使用起来也很灵活。这就把synchronized比下去了,在当时synchronized不管什么情况就是加锁、解锁,每次都要切换到内核态来做这些操作,导致CPU浪费了大量时间在线程的调度上。synchronized觉得不这不公平,我性能又差,而且还不灵活,那以后谁还用我!java一看,觉得不能偏心,就在jdk6的时候给synchronized进行了一个大优化,也就是上面提到的:引入偏向锁和轻量级锁。

偏向锁:

偏向锁是什么意思呢?故名思议,偏向某个东西的锁,而偏向的东西就是第一个获得锁的线程。因为在很多情况下,只有一个线程去访问临界区,或者多个线程对临界区并没有争夺。这时候如果还加锁的话无异于画蛇添足。本来就不会造成并发,还加什么锁来影响性能啊,这些情况就适合偏向锁。具体获得的过程如下:

一个线程访问了临界区(即需要同步进入的代码块),检查当前markword的最后两bit是否是01

如果不是的话,现在不是无锁或者偏向锁的状态,接下来讨论。

如果是的话,看一下当前倒数第三个bit是否是1

如果不是的话,现在是无锁的状态,将用CAS把线程ID指向当前线程,当前线程获得偏向锁。

如果是的话,现在已经是偏向锁了,不满足上面的条件了!这时候先找一个安全的时间停一下,先看看线程是不是自己,如果是自己的话就不用管,否则看看那个线程是否还在临界区里面,如果不在了,证明那个锁用完了,只是暂时还没来得及释放,此时将线程ID给这个当前线程就行了,如果还在的话则考虑升到轻量级锁。

以上就是偏向锁的过程了,在存在竞争且获得偏向锁的线程还处于临界区的时候就会省级到轻量级锁。

轻量级锁

轻量级锁是什么呢?先考虑一下这种情况。现在线程存在竞争了,但是他们的临界区执行的代码很可能就是i++这种非常快就能执行完的。那么这时候再进入内核态去调取线程实在是太浪费时间了,因为调度的时间可能都比代码运行时间长。这时候就出现了自旋锁的概念。所谓“自旋”,就是让线程去执行一个无意义的循环,循环结束后再去重新竞争锁,如果竞争不到继续循环,循环过程中线程会一直处于running状态。而轻量级锁就是用自旋锁这种机制来实现的。其加锁过程有以下两种:

①通过偏向锁升级

在上面偏向锁的情况中,如果存在着竞争且原有的线程没有退出临界区,则将偏向锁膨胀为轻量级锁。将前30位通过CAS尝试将头部的二进制位修改为“线程私有栈中对mark区域拷贝存放的地址”,如果成功,则会将最后2位设置为00,代表已经被轻量级锁锁住了。然后与其竞争的线程通过自旋来尝试获得锁。

②本来就是轻量级锁

获取锁的时候发现锁标志位是00,此时已经有人在使用着锁,这时候也会尝试用CAS替换Markword中前30位的指针,如果成功则代表获得了锁,如果失败则开始自旋,如果超过了自旋的限制还没有获得锁则膨胀为重量级锁。

解锁的过程如下:

解锁就是先尝试用CAS来将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败则表示还是有竞争发生,这时候轻量级锁处理不了了,就膨胀为重量级锁。

重量级锁

重量级锁就很容易理解,它是利用互斥monitor来实现的,markword存的是这个互斥量的指针,给同步代码块加锁时用monitorenter ,解锁时用monitorexit。且只有一个线程能monitorenter,其他的线程再想monitorenter就会进入阻塞队列,直到有monitorexit时才会从阻塞队列中调度线程。需要不断进入内核态来进行线程的调度,很耗时,但是保证了并发时数据的正确性和完整性。

至此,Synchronized的三种锁状态就结束了,这个难度对于我来说说深不深说浅不浅,刚刚好。如果你觉得太浅了,还希望进一步去了解可以去了解一下JVM,如果觉得有点深了,很多不懂可以先去了解一下CAS、monitor等待