• synchronized


    synchronized直接看这篇

    1,synchronized关键字用法

    ①修饰代码块,指定加锁对象,表示进入当前代码块要获得指定对象的锁。

    ②修饰实例方法:对当前对象加锁,进入同步方法要获得当前对象的锁。

    ③修饰静态方法,给当前类加锁,作用于类的所有对象,访问静态方法要获得class锁。

    2,介绍一下synchronized

    ①,解决多个线程访问资源的同步性,synchronized关键字保证它修饰的方法或者代码块在任意时刻只有一个线程访问。

    ②java早期版本sychronized是重量级锁,效率较低

    因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。

    如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,

    这个状态之间的转换需要相对比较长的时间,时间成本相对较高。

    ③后期优化,引入偏向锁,轻量级锁等。

    2.1,底层原理:

    ①java中每一个对象都可以作为锁,每一个Object对像都有一个Monitor监视器,

    ②synchronized同步语句块使用monitorenter和monitorexit指令分别指向同步代码块的开始与结束,当执行monitorenter时,尝试获取对象锁,

    对象锁计数器为0则表示该对象锁可获取,获取后,对象锁加1.

    ③当同步方法时,用到ACC_SYNCHRINIZED标识,表示该方法是同步方法。monitorenter和monitorexit指令与ACC_SYNCHRINIZED标识

    都是为了获取对象锁。

    3,synchronized(底层原理)

    syncronized用的锁存在java对象的对像头里。

    3.1,什么是对象头?

    ①Java对象在堆中的基本的基本结构分三部分(有点像计算机网络中报文或者帧的结构,前缀+数据+后缀):header(对相头)+实例数据+对齐填充。

    ②对象头,非数组对象头为两个字宽(32位一字宽位332bit,64位为64bit)为Mark Word(标识位,标识锁状态等信息,)部分和,Class Metadata Address

    (储存到对象类型数据的指针),如果是数组对象用三个字宽,加上数组长度Array length。

    ③Mark Word的储存结构:

    (锁轻量,重量,偏向指的是synchronized锁升级后引入的),对象头里储存锁状态信息,锁的类型,包括持有锁的线程ID等信息。

    其中重量锁(升级前只有重量锁)状态下,Mark Word中有指向对象关联的Monitor(监视器锁)的指针。偏向锁状态下,储存有当前

    持有该对象的线程ID.

    3.2,什么是Monitor?

     

    ①一种实现同步的工具,每个java对象都可以关联一个Monitor对象,(每个java对象都可以是锁。)

    ②是实现synchronized内置锁的关键。

    • Monitor对象如何与java对象关联,

    <1>Java对象被某线程获取锁,对象头Mark Word中会储存指向Monitor的指针。

    <2>Monitor对象的Owner字段会存放相关联的对象的获取其对象锁的线程ID,

    每个对象关联一个Monitor锁,Monitor锁,有一个阻塞队列,用于存放阻塞等待抢锁线程

     

    3.3,synchronized锁优化

    3.3.1synchronized是一种排他锁(只能有一个线程持有锁)也是一种可重入锁(已持有锁的线程再次获得锁。避免自己将自己阻塞,排他,不排自己)

    优化之前(加锁机制):

    ①如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的owner

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

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

    优化之后:

    引入偏向锁,轻量级锁,重量级锁,自旋锁。

    优化和通过synchronized加锁的过程是: 偏向锁-->轻量级锁-->重量级锁

    3.3.2偏向锁:为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径

    <1>即当某线程第一个获得某对象锁,设置为偏向锁,Mark Word中标识为偏向锁,并储存当前偏向线程ID,

    <2>当下一个线程来竞争锁的时候,先比较偏向线程ID,如果是偏向线程,直接获得锁,不需要指行3.3.1中的②步骤,

    对锁标识位每增加1,就要有一个-1与之对应。如果不是偏向线程,则先CAS操作抢锁,CAS成功(原偏向线程执行完或者中断退出)

    若CAS不成功,就产生竞争,准备撤销偏向锁。

    <3>撤销过程:(图片参考(复制)自synchronized原理

           

    3.3.3,轻量级锁:引入自旋锁,减少竞争失败立马阻塞带来的系统开销

    • 线程阻塞开销:

           java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,

    需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,

    专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的

    一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。

    • 但自旋(死循环,失败了不立马阻塞,接着抢一会,)同样带来CPU消耗,若以要规定自旋时长(重复抢锁次数)到一定

    还没抢到,升级重量级锁,在抢。

    <1>线程通过CAS获取锁,成功-获取锁执行。不成功则有竞争,进入自旋抢锁阶段,自旋一定次数仍抢不到表示竞争激烈

    升级为重量级锁。

    3.3.4,重量级锁:线程竞争失败会进入阻塞队列,挂起阻塞等待唤醒。

     

    3.3.5,优化总结:

    ①引入偏向锁的目的:在只有单线程执行情况下,尽量减少不必要的轻量级锁执行路径,轻量级锁的获取及释放依赖多次CAS原子指令,

    而偏向锁只依赖一次CAS原子指令置换ThreadID,之后只要判断线程ID为当前线程即可,偏向锁使用了一种等到竞争出现才释放锁的机制,

    消除偏向锁的开销还是蛮大的。如果同步资源或代码一直都是多线程访问的,那么消除偏向锁这一步骤对你来说就是多余的,

    可以通过-XX:-UseBiasedLocking=false来关闭
    ②引入轻量级锁的目的:在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗(用户态和核心态转换),但是如果多个

    线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁
    ③重入:对于不同级别的锁都有重入策略,偏向锁:单线程独占,重入只用检查threadId等于该线程;轻量级锁:重入将栈帧中lock record的header

    设置为null,重入退出,只用弹出栈帧,直到最后一个重入退出CAS写回数据释放锁;重量级锁:重入_recursions++,重入退出_recursions--,_recursions=0时释放锁

     

    3.4,synchronized锁,等待/唤醒机制:

    3.4.1,进入重量级锁阻塞队列的线程,需要外界来唤醒,

    Object类中:

    wait()               调用该对象的线程,进入WATING状态,只能等待另外线程唤醒。调用wait()后会释放对象锁。

    wait(long)        超时唤醒,若在设置时间(毫秒)内没有被其他线程唤醒,则自动唤醒位RUNNABLE状态,竞争抢锁。

    wait(long,int)    时间单位更精确(可达到纳秒)

    notify()              通知等待队列的一个线程,使其从wait()方法中返回,返回的前提是获取到对象锁

    notifyAll()          通知所有该对象等待队列线程。

     

    3.4.2,wait(),和sleep()的区别:

        • wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
        • wait() 会释放锁,sleep() 不会

      

     

    参考:

    synchronizeed原理-很全

    深入理解java中的锁

    深入浅出多线程

    javaguide

     

     

  • 相关阅读:
    【队列应用一】随机迷宫|随机起点终点*最短路径算法
    【堆栈应用二】迷宫算法,可直接动态运行
    【堆栈应用一】一个数divided=几个最小质因数的乘积(时间复杂度On)
    MyEclipse2014中Java类右键Run as没有JUnit Test
    C++中break/Continue,exit/return的理解
    windows下用C++修改本机IP地址
    windows下注册表的操作
    详解Java的Spring框架中的注解的用法
    什么是Java Bean
    JS windows对象的top属性
  • 原文地址:https://www.cnblogs.com/wangpan8721/p/13782670.html
Copyright © 2020-2023  润新知