Java中在多线程的环境下,多线程并发的操作可能会导致某些变量发生数据不一致的情况,那么如何去保证线程安全,更好的使用多线程呢?可以使用同步锁,但是同步锁在保证线程安全的同时,也会导致程序的并发性降低,操作比较重量级,JVM是如何对锁进行优化,来保证锁的效率和功能呢?一起来学习JVM关于线程安全与锁优化的相关知识吧~
线程安全
线程安全的实现方法
-
互斥同步
含义:互斥同步又叫阻塞同步,简单来讲就是让本来是多线程执行的操作变为单线程,即保证共享数据在同一时刻制备一个线程使用。实现方式:
-
synchronized关键字
Syncrhonized是最基本的同步手段,由JVM层面来控制程序的同步,synchronized关键字在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。保证获取到monitor对象之后,在同步块的范围内只会有一个线程执行,其他的线程在没有获取到锁时,会处于阻塞阶段。
-
Lock接口
JUC包下的reentrantlock也可以实现同步,当线程获取到锁时可以正常执行,没有获取到锁的线程会自旋等待或者阻塞。其中
reentrantlock
的实现方式为API层面,通过AQS(后面会有文章单独对AQS进行解析哦~),可以理解为等待队列和state变量、CAS操作保证线程的锁机制,实现同步。
-
-
非阻塞同步
因为线程的阻塞和唤醒操作是非常重量级的操作,需要在用户态和内核态之间转换,所以通过非阻塞同步的方案,实现线程同步的机制,提高线程的并发性。
-
无同步方案
无需使用锁就可以保证线程的安全性,主要有两种方式:
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中需要释放锁来保证不会阻塞其他线程
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。