sync的锁

锁范围?

   静态: 是类对象加锁.同一个的实例都不行.但是单独的类实例加锁的方法可以访问.默认当前对象为锁

   非静态: 类实例加锁.同一个类不同的实例,相当于锁不同.

   其他线程除了不能访问synco加锁的本方法外,也不能访问,其他加同样锁的synco方法

   代码块加锁: 可以指定锁的实例,锁对象.为锁的监视器


  锁的4中状态:

      无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高)

  偏向锁?

   为什么有偏向锁?

        是因为多个线程竞争同一个锁的场景少,大部分是同一个线程多次重入竞争一个锁

        为了降低获取锁(CAS设置锁)的代价,引入了偏向锁.

        总是会偏向第一个进行访问的线程,如果运行过程只有一个线程,不存在同步竞争线程.就会给该线程加一个偏向标识.

       如果,运行过程中,出现其他线程来竞争锁,就会挂起当前的偏向线程,消除偏向的标识.恢复到轻量级锁

偏向锁实现的过程:

     1.访问markword的部分,查看锁标识位是否是01,偏向标识是否为1.确认可偏向状态

    2.校验当前线程是不是正常运行,偏向线程id是不是当前线程,是执行3,不是3

   3.偏向锁线程非当前线程,通过CAS操作设置偏向线程为当前线程.成功设置,执行5,不成功4

   4.设置失败,说明有竞争,升级为轻量级锁.(如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,

      偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。(撤销偏向锁的时候会导致stop the word))

    5.同步代码执行

偏向锁的释放:

  当遇到有其他线程竞争锁,会发生偏向锁释放,线程本身不会释放偏向锁

    偏向锁的撤销,会等待达到一个全局安全点(没有字节码在执行),会首先暂停持有偏向锁的线程,判断当前锁的状态是否锁定,

    撤销偏向锁的标识位未锁定(01),或轻量级锁(标志位为“00”)的状态

偏向锁的使用场景?

    适用只有一个线程在执行同步代码的.这个线程不执行完释放锁,不会有其他线程进来执行同步代码.

    一旦竞争,偏向锁会多做很多额外操作,尤其是撤销偏向所的时候会导致进入安全点,安全点会导致stop the word,导致性能下降,这种情况下应当禁用;

轻量级锁? 线程不阻塞,CAS自选等待竞争锁

    为什么有轻量级锁?

         是因为有的场景,线程执行任务的时间不长,持有锁时间短,但是开启的线程不多.如果多线程竞争时,把等待的线程进行阻塞到队列,线程从阻塞到可运行切换,这样就会加大cpu从用户态到内核态的切换,减少不必要的性能开销.如果,线程刚刚阻塞,获取锁的线程就执行完释放锁,就会浪费资源,所以,线程干脆不进行阻塞,进行CAS自旋获取锁.

    但是自旋的太长时间也会消耗cpu,所以,自旋的次数是由设置的,达到一定的次数,会升级锁为重量级锁.

   线程一还在执行,线程二还在自旋等待,此时线程三也来竞争,就会升级成重量级锁,将除了运行的线程之外的线程阻塞,放在CPU空转消耗\

轻量级锁的过程?

   线程在请求执行代码块的时候,会判断对象锁的头部信息的markword的锁的标识位是不是01(无锁),锁状态0,非偏向锁.

   然后在当前线程的栈帧里面建一个记录,(拷贝锁对象)用于存储锁对象目前的Mark Word的拷贝,将markword存放到锁记录位置.

  拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word

   CAS成功,将对象锁的锁标志位更新成00,轻量级锁

   CAS失败,会比较检查锁的markword是否指向了当前线程的栈帧,是,执行同步代码块,认为已经有锁.

    否,说明多个线程竞争锁,轻量级锁膨胀位重量级锁,标志位10.markword种就是重量级锁的指针.阻塞其他线程


轻量级锁的释放?

      释放锁线程视角:

          由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的markword,

           在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对markword做了修改,两者比对发现不一致,则切换到重量锁。

           因为重量级锁被修改了,所有display mark word和原来的markword不一样了。

           怎么补救,就是进入mutex前,compare一下obj的markword状态。确认该markword是否被其他线程持有。

           此时如果线程已经释放了markword,那么通过CAS后就可以直接进入线程,无需进入mutex,就这个作用。

    尝试获取锁线程视角

         如果线程尝试获取锁的时候,轻量锁正被其他线程占有,那么它就会修改markword,修改重量级锁,表示该进入重量锁了。

         还有一个注意点:等待轻量锁的线程不会阻塞,它会一直自旋等待锁,并如上所说修改markword。(自旋的时候,拿到和查询的一致,就可以设置修改markword为自己设置的)

这就是自旋锁,尝试获取锁的线程,在没有获得锁的时候,不被挂起,而转而去执行一个空循环,即自旋。

在若干个自旋后,如果还没有获得锁,则才被挂起,获得锁,则执行代码

重量级锁:

    除了获取锁正在运行的线程之外,其他竞争的线程都进行阻塞.用于线程多,线程执行任务长.减少了CPU自旋的消耗

synck加锁的过程?

     1.首先判断对象头的markword里面的线程id是不是当前线程id,是当前线程id,说明该线程持有偏向锁

     2.不是1,CAS操作将当前线程设置称markword的偏向线程id.偏向标志位设置1,成功,持有偏向锁,

     3.不成功升级成轻量级锁,首先释放偏向锁,之后,CAS在当前线程的栈帧锁记录markword的信息,markword的锁标志位00,指向锁记录的指针.成功,持有轻量级锁

     4.如果3失败,说明有竞争,会自旋获取锁,自旋成功,获取轻量级锁

     5.4失败,升级称重量级锁


为什么用户态到内核态切换消耗系统资源?

    一个线程阻塞的代价,阻塞到唤醒需要用户态到内核态切换

    因为内核态和用户都有各自专用的内存空间和专用的寄存器,切换时,需要传递很多的变量,和状态给内核,内核也需要维护这些状态.内核也要切换到用户态,用户态继续工作

     所以很消耗资源


在了解锁之前,要了解下.java对象的结构

    对象结构: header,data,padding

    header头,也称对象头.对象头分为:

            markword:用来存储运行时自身的数据,hash码,分代年龄,偏向锁id,偏向锁线程id,自身锁标识位,锁状态,持有锁的线程

            klass:指向类的class,永久代元数据的class的指针是,标识是哪个类的实例

            数组长度:如果是数据类型,还记录数组长度

  data:真正存储的有效信息,存储对象的属性,父类的属性信息

  padding:没有实际意义.就是为了对齐字节.字节是8的整数倍.虚拟机自动管理内存地址的要求是8的整数倍


  对象A a=new A();时,首先会通过类加载器看有没有加载过A类,加载过的话,会在堆内存分配一块空间.

  如果这个类没有被加载过,JVM就会进行类的加载,并在JVM内部创建一个instanceKlass对象表示这个类的运行时元数据(相当于Java层的Class对象)。

  初始化对象的时候(执行invokespecial A::),JVM就会创建一个instanceOopDesc对象表示这个对象的实例,

  然后进行Mark Word的填充,将元数据指针指向Klass对象,并填充实例变量。

你可能感兴趣的:(sync的锁)