java同步锁synchronized用法,java synchronized死锁

  java同步锁synchronized用法,java synchronized死锁

  00-1010 casmarkwords简介同步锁升级偏向锁轻量级锁重量级锁摘要

  00-1010 Synchronized常用于解决多线程中的线程安全问题。相比早期的synchronized,现在的synchronized进行了优化,从之前的加锁到重量级加锁再到一个锁的升级过程(偏向锁-轻量级锁-重量级锁)。

  

目录

cas的全称是compare and swap.从名字就可以看出是设置前比较的,是多线程环境下实现同步的一种机制。

 

  下面这段代码是在ReentrantLock类中复制的关于CAS操作的代码。

  protected final boolean compareAndSetState(int expect,int update) { //支持此返回unsafe.compareAndSwapInt(this,stateOffset,expect,update)的内部函数设置见下文;} compareandswapint的参数。这里的参数1和2现在被理解为参数不安全。compareandswapint (curr,expect,update);所以这个Cas操作需要三个参数。

  需要修改参数一:,的当前值、参数二:的预期值和参数三:的预期值。只有当前值与期望值一致时,当前值才会被修改为参数三传入的值。

  CAS广泛应用于JUC包装。比如AtomicXXX类中使用了大量的CAS操作。

  CAS不难理解,有个概念就好。

  00-1010如果知道对象的内存布局,可以跳过这一段。这个对象的内存布局与JVM的实现有关。本章是关于HotSpot的实现。

  当一个对象被创建出来后它在内存中的布局如下,由四部分组成:

  8字节markword,(markword包含其他东西,比如GC标记和锁类型)4字节ClassPoint(这个指针指向的类)。默认情况下,指针压缩是打开的,所以它是4个字节。关闭指针压缩后,填充8字节实例对象中成员属性的大小字节(部分JVM需要8字节对齐,如果上述字节相加后不能被8整除,则需要在此处填充)

  看到上图,应该可以大致看出,同步锁其实就是头中修改标记字的数据。所以synchronized可以锁定任何对象。

  现在有一个Java类t,更新后它的对象的内存布局是怎样的?

  T类{整数年龄;}可以通过一个小工具查看这个T类在内存中的对象布局。

  依赖groupIdorg.openjdk.jol/groupId神器Jol-core/Artifact ID 0.9版/版本范围编译/范围/依赖通过下面的程序打印T对象的布局是什么样子的。

  public static void main(String[]args){ T o=new T();system . out . println(class layout . parse instance(o))。top printable());}

  这张图片是一个未锁定对象的对象布局。

  同步后的对象布局是什么样的?这一次,修改T类,使其具有字节填充。

  java;">class T{ Integer age; Integer age1;}public static void main(String[] args) { T o = new T(); synchronized (o){ System.out.println(ClassLayout.parseInstance(o).toPrintable()); }}

 

  到这里可能有些小伙伴有疑问,这里为啥是轻量级锁,不应该先是偏向锁吗?原因如下:

  因为偏向锁是有4秒的延迟的,所以如果想要看到效果可以在代码里加上sleep(4100)就可以了。或者是通过jvm参数-XX:BiasedLockingStartupDelay=0将延迟设置成0

  

 

  

看完这里也对markword有了些了解了,因为在synchronized中加锁就是通过cas的方式修改的markword中的锁状态

 

  

 

  

Synchronized的锁升级

 

  上图大概就是Synchronized加锁后的一个锁升级的过程。从早期的重量级锁优化到了现在一个轻量级锁。

  

 

  

偏向锁

上面的重量级锁说到重量级锁想要申请一把锁需要用户态到内核态的一个转换,到了后期的JDK版本中,加锁不用在去向OS去申请锁了,只需要在用户态就可以完成加锁。

 

  从名字上可以看出偏向锁它就是偏向某一个线程,把这个锁加到这个线程上,在加锁的时候如果发现当前锁的竞争线程只有一个线程的话,那么这个锁直接偏向这个线程。直接上锁,不存在竞争。并在线程栈中创建一个LR(锁记录)并将markword拷贝到LR中,同时锁中的markwrod中的指针也会指向当前持有锁线程的LR

  

这里的LR是有什么作用?

 

  首先synchronized是一个可重入锁,它即然是一个可重入锁它就得有一个东西用来记录重入的次数(加锁几次必须解锁几次)。在解锁时LR在栈中弹出一个就表示解锁一次。

  

当有多个线程竞争的时候会升级成轻量级锁(自旋锁)

 

  通过下图来看下偏向锁是怎么一回事。

  

 

  当大呆需要上WC时,只有它自已要上WC,此时并没有其它的人需要上WC,那么这时这个WC可以直接给大呆使用,并且大呆把可以标识自已身份的ID贴到门上,表示此时大呆占用了这个WC。

  当又有一个线程来抢占锁时发现当前锁已被占用,此时锁会从偏向锁升级成轻量级锁。

  匿名偏向

  在执行的时候将偏向锁的延迟设置成0-XX:BiasedLockingStartupDelay=0

  

 public static void main(String[] args) throws InterruptedException { T o = new T(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); }

 

  可以看这个程序的执行结果,当前的锁状态是偏向锁,而有意思是的锁存在,但是他并没有指向线程的指针,

  这种情况称为匿名偏向。

  

 

  

轻量级锁

说到轻量级锁可能需要在两种情况下来说它,一是在升级成轻量锁之前有偏向锁,另一种是在升级轻量锁之前没有偏向锁,这里说完第一种第二种不用解释各位也会明白是怎么一回事。

 

  

 

  还是用上面这个图来解释,此时当前的WC被大呆所占用,这时二呆来了也要使用WC。这时大呆和二呆就要通过CAS的方式来抢占WC。

  因为此时锁的状态是偏向锁的状态,二呆来了也要使用WC(这时有两个人同时要使用WC,这时就要将偏向锁升级成轻量级锁),在升级轻量锁之前首先需要将WC上的标识大呆身份的ID撕下来(这一步叫做偏向锁的撤销),然后能过自旋+CAS的方式两个人来抢锁。当其中一个线程抢锁成功后,会将LR贴到WC的门上,表示WC当前被某个线程占用,然后另一个没有抢到锁的线程就一直自旋,当自旋一定次数后升级成重量级锁。

  如果在升级轻量锁之前没有偏向锁,此时两个线程直接自旋+CAS的方式来抢锁。

  

 

  

重量级锁

在了解重量级锁之前,我想应该先说下用户态与内核态

 

  对于系统而言,它可以做的一些事情,普通的应用程序是无法完成的,比如系统可以干掉硬盘,如果普通的程序想要干掉硬盘它必须向操作系统去申请,由此操作系统中的指令分了级别,操作系统级别可以访问所有的指令,在用户态下只能访问用户能访问的指令,如果用户态要访问内核态可以执行的指令必须去向操作系统去申请,请操作系统调用。

  

 

  在JDK早期,上锁只能上重量级锁。因为,所谓的JVM其实它也是工作在用户态的一个进程,如果想要对一个对象进行上锁,那它必须去向系统去申请锁。申请锁成功后,还需要将这把锁从内核态返回到用户态,它称为重量级锁的原因就是在锁申请的时候都要有一个在用户态到内核态的转换

  

 

  当抢占到锁后,markword里面记录的不再是LR的指针,而是指向的是一个C++的对象ObjectMonitor,

  如果当前线程自旋一段时间后没有抢到锁就会升级成重量级锁,并将当前的线程存入EntryList队列中阻塞,持有锁的线程执行完成后,在唤醒EntryList队列中的线程去抢占锁。

  

 

  

总结

synchronized的锁升级过程:

 

  偏向锁未启动,创建出来的是普通对象, 如果有一个线程来抢占锁,该锁偏向此线程,这时升级为偏向锁。在偏向锁的基础上又来一个线程抢占锁此时升级为轻量级锁。当一个线程没有抢占到锁,并且自旋了一定时间后还没有抢到锁,就会升级成重量级锁。在偏向锁的基础上如果出现了重度竞争就会直接升级成重量级锁偏向锁已启动,创建出来的对象匿名偏向,后面的锁升级和上面写的一样。到此这篇关于Java中synchronized锁升级的过程的文章就介绍到这了,更多相关synchronized锁升级内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: