一、介绍
1. 简介
ReentrantLock是一种基于AQS(Abstract Queued Synchronizer)框架的应用实现,是JDK中一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。
2. 是什么类型的锁
(1) 公平锁或非公平锁(下面案例中有实际代码,自己执行一遍更容易理解)
a. 公平锁:先来的线程先执行,排成排按顺序。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
b. 非公平锁:后来的线程有可能先执行,可插队不一定按顺序。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
(2) 互斥锁
一次只能执行一个线程。
(3) 可重入锁
同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
比较抽象,解释下:假如景区有3个收费项目,如果每个项目单独玩都需要收费。但是你买了个VIP。进入景区大门的时候工作人员为了方便,直接给你挂了个牌子,里面三个项目的工作人员看到你的牌子,就认为你已经买过该项目的门票,不在向你收费,进去就行。
3. 优点
(1) 可中断并且可以设置超时时间。
(2) 可以根据业务场景使用公平锁或非公平锁。
(3) 获取锁可设置超时。
(4) 可绑定多个条件(Condition)。
4.主要方法
getHoldCount():当前线程调用 lock() 方法的次数。
getQueueLength():当前正在等待获取 Lock 锁的线程的估计数。
getWaitQueueLength(Condition condition):当前正在等待状态的线程的估计数,需要传入 Condition 对象。
hasWaiters(Condition condition):查询是否有线程正在等待与 Lock 锁有关的 Condition 条件。
hasQueuedThread(Thread thread):查询指定的线程是否正在等待获取 Lock 锁。
hasQueuedThreads():查询是否有线程正在等待获取此锁定。
isFair():判断当前 Lock 锁是不是公平锁。
isHeldByCurrentThread():查询当前线程是否保持此锁定。
isLocked():查询此锁定是否由任意线程保持。
tryLock():线程尝试获取锁,如果获取成功,则返回 true,如果获取失败(即锁已被其他线程获取),则返回 false。
tryLock(long timeout,TimeUnit unit):线程如果在指定等待时间内获得了锁,就返回true,否则返回 false。
lockInterruptibly():如果当前线程未被中断,则获取该锁定,如果已经被中断则出现异常。
方法详细介绍可以看最下面参考文章:ReentrantLock类中的方法
使用时注意事项
(1) 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。
(2) 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
说明:线程一需要对表A、B、C依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是A、B、C,否则可能出现死锁。
(3) 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。
a. 在lock方法与try代码块之间的方法调用抛出异常,无法解锁,造成其它线程无法成功获取锁。
b. 如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。
c. 在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。
d. 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。
说明:Lock对象的unlock方法在执行时,它会调用AQS的tryRelease方法(取决于具体实现类),如果当前线程不持有锁,则抛出IllegalMonitorStateException异常。