zoukankan      html  css  js  c++  java
  • Java并发编程总结(一)Syncronized解析

    Syncronized解析

    作用:

    1)确保线程互斥的访问同步代码

    2)保证共享变量的修改能够及时可见

    3)有效解决重排序问题。

    用法:

    1)修饰普通方法(锁是当前实例对象

    2)修饰静态方法(锁是当前对象的Class对象

    3)修饰代码块(锁是Synchonized括号里配置的对象

     

    底层实现原理:

          方法和代码块都是基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。

     

    (1)代码块同步是使用monitorentermonitorexit指令实现,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处

    每个对象有一个监视器锁(monitor)。线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

    1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

    2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

    3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

     

    执行monitorexit的线程必须是锁对象所对应的monitor的所有者。

    指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。 

     

    (2)方法同步是使用ACC_SYNCHRONIZED标示符实现的

           相对于普通方法,常量池中多了ACC_SYNCHRONIZED标示符。当方法调用时,将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

     


    讲jdk1.6锁优化前的前置知识



    锁的状态保存在java对象头中,那对象头是什么?

    对象在堆内存中存储的布局可以分为3块区域:对象头、实例数据、对齐补充。(jvm知识)

     

    对象头结构如下:


    CASCompare and Swap,比较并设置。用于在硬件层面上提供原子性操作。比较是否和给定的数值一致,如果一致则修改,不一致则不修改。



    Synchronized在jdk1.6的优化



    Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的,这个成本非常高,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为重量级锁JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了轻量级锁偏向锁

     

    锁的状态总共有四种:

    1.重量级锁(依赖操作系统的互斥锁)

    2.轻量级锁(在无竞争的情况下使用CAS操作去消除同步使用的互斥量,默认开始就是这个)

    3.偏向锁(在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了

    4.无锁状态

     

    随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)

     

    锁的状态保存在java对象头中,以32位的JDK为例:

     

     

    无锁和重量级(操作系统的互斥锁)不用解释了,重点解释轻量级锁和偏向锁。

     

    轻量级锁

    轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中。然后线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败且MarkWord没指向这个线程(指向这个线程则是重入,继续运行即可),表示其他线程占用了锁,当前线程便尝试使用自旋来获取锁,当自旋至一定次数或竞争的线程数大于2时,膨胀为重量级锁。

    轻量级锁解锁:轻量级解锁时,会使用原子的CAS操作来将栈帧中DisplacedMark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

     

    偏向锁

    默认是开启的,锁对象第一次被线程获取的时候,会把标志位设置一下01,偏向模式。

     

    偏向锁加锁过程:

    当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作

     

    偏向锁的撤销:

    当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。 根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就如上面介绍的轻量级锁那样执行

    三种锁状态的总结



    相关的扩展:

    1.  Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

     

     

    2.上文提到的偏向锁、轻量级锁都是jdk1.5到1.6的锁优化技术中的。锁优化技术还有如下:

    适应性自旋:

             互斥同步对性能最大的影响是阻塞的实现,因为挂起线程和恢复线程都是OS内核态完成的。那么使用自旋(即忙循环等待),不去阻塞地等待,这就是自旋锁技术。然而,自旋需要占用CPU时间,时间短还好,时间长了浪费性能,而适应性地根据同一个锁上的自旋时间及锁的拥有者状态决定时间,就是适应性自旋技术。

    锁消除:

             即时编译期运行时,有些代码上的同步,被检测到不可能存在数据竞争的锁,进行消除。检测依据来源于逃逸分析技术。

    锁粗化:

           如果一个方法里有A、B、C三句代码,都分别进行了三次Synchronized(this),其实没必要,反而增加了加锁解锁的消耗,这种情况会直接粗化为一个大Synchronized


    参考:

    《深入理解Java虚拟机》

    《Java并发编程艺术》

    http://www.cnblogs.com/paddix/p/5405678.html

    http://ifeve.com/java-synchronized/


  • 相关阅读:
    windows下的IO模型之选择(select)模型
    tcp通讯中socket套接字accept和listen的关系
    转一篇shell中关于各种括号的讲解
    记两个std接口equal_range,set_difference
    nginx学习
    c++ 读取文本问题
    vim使用常看
    CNN设计一些问题
    什么是反射?反射机制的应用场景有哪些?
    java为什么只有值传递?
  • 原文地址:https://www.cnblogs.com/chz-blogs/p/9380925.html
Copyright © 2011-2022 走看看