1、JDK1.5以后,Java提供了Lock同步锁,相对与需要JVM隐士获取和释放锁Synchronized 同步锁,Lock同步锁需要显示获取和释放锁,这就为获取和释放锁提供了更多的灵活性。
2、Lock锁的基本操作通过乐观锁来实现,但是由于Lock锁也会在阻塞的时候被挂起,因此它依然属于悲观锁。
对比Synchronized和Lock如下图
Synchronized | Lock | |
实现方式 | JVM层实现 | Java底层代码实现 |
锁的获取 | JVM隐士获取 | Lock.lock() 获取锁,如被锁定则等待。 Lock.tryLock() 如未被锁定才能获取。 Lock.tryLock(long timeout, TimeUnit unit) 获取锁,如被锁定,则最多等待timeout时间后返回获取锁状态。 Lock.lockInterruptibly() 如当前线程未被interrupt才获取锁。 |
锁的释放 | ||
锁的类型 | JVM隐士释放 | 通过Lock.unlock(),在finally中释放 |
锁的状态 | 不可中断 | 可中断 |
性能方面,并发不高、竞争不激烈的情况下,Synchronized和Lock的效率差不多,但是在高负载、高并发的情况下,Synchronized同步锁由于竞争激烈会升级到重量级锁,性能没有Lock稳定。
Lock锁的实现原理
Lock锁基于Java实现的锁,Lock是一个接口类,常用的实现类有ReentrantLock、ReentrantReadWriteLock,都是依赖与AQS类实现的。AQS类结构中主要包含一个基于链表实现的等待队列CLH,用于存储所有阻塞的线程;另外一个就是state变量,来表示加锁状态。
下图为ReentrantLock的一个简单的整体获取锁的过程
ReentrantLock为读写互斥锁,但在大部分场景中,读业务操作要远大于写业务操作,并且在多线程编程中,读操作不会修改共享资源的数据,如果多个线程仅仅是读取共享资源的化,就没有必要对该共享资源加互斥锁,加了反而影响性能。
ReentrantReadWriteLock应用而生,针对读多写少的场景,Lock接口提供了RRW,同一时间允许多个读线程同时访问,但是不允许读写,写写线程同时访问。RRW内部维护了两个锁,一个读锁ReadLock,一个WriteLock锁。
RRW如何保证共享资源的原子性:RRW也是基于AQS实现的,需要在同步状态state上维护多个读线程和一个写线程,RRW很好的利用了高低位,来实现一个整型控制两种状态的功能,读写锁将变量切分承两部分,高16位表示读,低16位表示写。
当一个线程尝试获取写锁时,首先判断同步状态 state 是否为 0。如果 state 等于 0,说明没有其它线程获取锁;如果 state 不等于 0,则说明有其它线程获取了锁。此时再判断同步状态state低16位d是否为0,如果d为0,则说明其它线程获取了读锁,此时当前线程则进入CLH进行阻塞等待,如果d不为0,则说明其他线程获取的写锁,此时要判断获取了写锁的线程是否为当前线程,如果是,就应判断当前线程获取写锁是否超过了最大次数,如抛异常,如果没有超过最大次数,则更行同步状态。
流程图
当一个线程尝试获取读锁时,同样会先判断state是否为0,如果state为0,说明没有其他线程获取锁,此时判断是否需要阻塞,如果需要阻塞则进入CLH队列进行阻塞,如果不需要,则CAS更新同步状态。如果state不为0,先判断低16位d是否为0,如果d不等于0,则获取读锁失败,进入CLH阻塞队列,反之判断当前线程是否应该被阻塞,如果不需要阻塞,则尝试CAS同步状态。获取成功更新同步锁为读状态。
流程图
未完待续 2022-01-10