ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

【一起学习JVM】Java中的线程安全

2021-03-21 23:04:14  阅读:170  来源: 互联网

标签:同步 Java CAS ReentrantLock 获取 state 线程 JVM


Java中在多线程的环境下,多线程并发的操作可能会导致某些变量发生数据不一致的情况,那么如何去保证线程安全,更好的使用多线程呢?可以使用同步锁,但是同步锁在保证线程安全的同时,也会导致程序的并发性降低,操作比较重量级,JVM是如何对锁进行优化,来保证锁的效率和功能呢?一起来学习JVM关于线程安全与锁优化的相关知识吧~

线程安全

线程安全的实现方法
  • 互斥同步
    含义:互斥同步又叫阻塞同步,简单来讲就是让本来是多线程执行的操作变为单线程,即保证共享数据在同一时刻制备一个线程使用。

    实现方式:

    • synchronized关键字

      Syncrhonized是最基本的同步手段,由JVM层面来控制程序的同步,synchronized关键字在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。保证获取到monitor对象之后,在同步块的范围内只会有一个线程执行,其他的线程在没有获取到锁时,会处于阻塞阶段。

    • Lock接口

      JUC包下的ReentrantLock也可以实现同步,当线程获取到锁时可以正常执行,没有获取到锁的线程会自旋等待或者阻塞。其中ReentrantLock的实现方式为API层面,通过AQS(后面会有文章单独对AQS进行解析哦~),可以理解为等待队列和state变量、CAS操作保证线程的锁机制,实现同步。

  • 非阻塞同步

    因为线程的阻塞和唤醒操作是非常重量级的操作,需要在用户态和内核态之间转换,所以通过非阻塞同步的方案,实现线程同步的机制,提高线程的并发性。

    • CAS操作

      CAS(compare and swap)比较并交换就是一种乐观锁策略,在实现同步的过程中,并不需要将线程阻塞。

      **含义:**CAS是Compare And Swap的简称,从字面上理解就是比较并交换,简单来说:从某一内存上取值V,和预期值A进行比较,如果内存值V和预期值A的结果相等,那么我们就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止

      CAS操作是CPU级别的指令,原子性由CPU等硬件进行保证,是一个原子性操作,可以保证CAS操作过程中的数据的一致性。

    • CAS的缺点

      • ABA问题
      • 只能保证一个变量的原子性
      • 无效的忙循环
  • 无同步方案

    无需使用锁就可以保证线程的安全性,主要有两种方式:

    • ThreadLocal

      将需要和其他线程共享的数据放到ThreadLocal中,限制数据的可见范围在同一个线程中。让数据变为线程私有的数据,那么就不存在数据需要同步的操作

    • 纯代码

      纯代码即不依赖全局变量、存储在堆中的数据和公用的系统资源,用来的状态量都是参数中传入的。只要输入了相同的数据,那么任意情况下都返回相同的结果

      纯代码不依赖堆中的数据,使用到的变量都是栈中的,线程私有的,可以保证线程的安全性,无需同步。

Synchronized实现原理

  • 实现原理

    sychronized底层是通过Monitor对象来实现锁机制的,在sychronized关键之前,线程会先执行monitorenter指令(JVM指令),则先去获取锁,如果可以获取到锁,则将Monitor对象中的持有锁owner修改(CAS操作)为当前线程id(owner表示持有锁的线程),并将state变量+1(state是当前线程获取锁的次数,保证了锁的可重入性)。其他的没有获取到锁的线程会进入到Monitor对象的EntryList队列中等待。当前线程在退出sychronized的范围时,会执行monitorexit指令,会将state-1,如果state=0,则释放锁,将Monitor对象中的owner重置,并唤醒等待队列中的线程继续抢夺锁。

  • Monitor结构

     ObjectMonitor() {
        ......
        // 用来记录该线程获取锁的次数
        _count        = 0;
         // 锁的重入次数
        _recursions   = 0;
        // 指向持有ObjectMonitor对象的线程
        _owner        = NULL;
        // 存放处于wait状态的线程队列
        _WaitSet      = NULL;
        // 存放处于等待锁block状态的线程队列
        _EntryList    = NULL ;
        ......
      }
    

ReentrantLock实现原理

  • 实现原理

    ReentrantLock底层是通过鼎鼎大名的AQS来实现,无论是公平锁还是非公平锁,都是通过AQS实现,而AQS的实现,简单说就是基于volatile修饰的state变量和CLH队列以及CAS操作实现(CLH队列可以理解为链表实现的双端队列)

    当线程获取锁时,会先判断当前线程是否已经获取到锁,如果是重入线程,那么就将state++直接获取到锁,如果不是重入线程,并且当前state=0(表示锁没有被其他线程获取),那么就通过CAS操作获取锁,如果将state修改为1,表示获取到锁,记录当前获取锁的线程。如果CAS操作失败,那么就将当前线程加入到CLH队列中,并通过自选的方式判断前一个节点是否为头结点,如果是头节点,则尝试获取锁,当自旋获取不到锁,则阻塞等待。当释放锁时,将state--,如果当前的state=0,那么就释放锁,并将队列中的下一个线程唤醒。

彩蛋

  • 说一说Synchronized和ReentrantLock的异同点
  • 相同点
    • synchronized和ReentrantLock(默认)都是非公平锁、可重入锁
    • 不同点
      • synchronized是JVM层面的锁,是一个关键字
      • ReentrantLock是Lock接口的实现类,是Java中的API
      • ReentrantLock可以设置为公平锁,而Synchronized只能是非公平锁
      • ReentrantLock可以绑定多个条件对象,而Synchronized是通过锁对象的wait()notify()notifyAll()方法配合实现锁条件。
      • Synchronized由JVM来控制加锁和释放锁的操作,并且在异常发生的时候,也回释放锁,ReentrantLock必须通过手动加锁、释放锁、并且在finally中需要释放锁来保证不会阻塞其他线程

标签:同步,Java,CAS,ReentrantLock,获取,state,线程,JVM
来源: https://blog.csdn.net/weixin_42054155/article/details/115058253

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有