【Java 并发笔记】ReentrantLock 相关整理
文前说明
作为码农中的一员,需要不断的学习,我工作之余将少量分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
1. ReentrantLock
ReentrantLock就可重入锁,实现了 Lock 和 Serializable 接口。- 在 Java 环境下
ReentrantLock和synchronized都是可重入锁。- 可重入锁,也叫做递归锁,当一个线程请求得到一个对象锁后再次请求此对象锁,可以再次得到该对象锁。
- 调用
get()方法,同一个线程 ID 会被连续输出两次。
public void get() { lock.lock(); System.out.println(Thread.currentThread().getId()); set(); lock.unlock(); }public void set() { lock.lock(); System.out.println(Thread.currentThread().getId()); lock.unlock();}ReentrantLock构造函数中提供了两种锁:创立公平锁和非公平锁(默认)。- RentrantLock 有三个内部类 Sync、NonfairSync 和 FairSync 类。
- Sync 继承 AbstractQueuedSynchronizer 笼统类。
- NonfairSync(非公平锁) 继承 Sync 笼统类。
- FairSync(公平锁) 继承 Sync 笼统类。
public ReentrantLock() { sync = new NonfairSync();}public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}- 在公平的锁上,线程按照他们发出请求的顺序获取锁,但在非公平锁上,则允许插队。
- 当一个线程请求非公平锁时,假如在发出请求的同时该锁变成可用状态,那么这个线程会跳过队列中所有的等待线程而取得锁。
- 非公平的
ReentrantLock并不提倡插队行为,但是无法防止某个线程在合适的时候进行插队。
- 在公平的锁上,假如有另一个线程持有锁或者者有其余线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。
- 而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。
- 非公平锁性能高于公平锁性能的起因
- 在恢复一个被挂起的线程与该线程真正运行之间存在着严重的推迟。
- 假设线程 A 持有一个锁,并且线程 B 请求这个锁。因为锁被 A 持有,因而 B 将被挂起。当 A 释放锁时,B 将被唤醒,因而 B 会再次尝试获取这个锁。与此同时,假如线程 C 也请求这个锁,那么 C 很可能会在 B 被完全唤醒之前取得、使用以及释放这个锁。这样就是一种双赢的局面,B 取得锁的时刻并没有延迟,C 更早的取得了锁,并且吞吐量也提高了。
- 当持有锁的时间相对较长或者者请求锁的平均时间间隔较长,应该使用公平锁。
- 在这些情况下,插队带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)可能不会出现。
1.1 公平锁与非公平锁实现
公平锁
- 公平锁表示线程获取锁的顺序是按照 线程加锁的顺序 来分配的,即先来先得的 FIFO 先进先出顺序。
- 每个线程获取锁的过程是公平的,等待时间最长的会最先被唤醒获取锁。
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }}- 公平锁
tryAcquire()方法比非公平锁nonfairTryAcquire()方法多了一个hasQueuedPredecessors()方法。- hasQueuedPredecessors 方法判断了头结点能否为当前线程。
public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());}非公平锁
- 而非公平锁就是一种获取锁的抢占机制,是 随机取得锁 的,和公平锁不一样的就是先来的不肯定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。
- 非公平锁可能使线程 ” 饥饿 “。
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }}- 公平和非公平都指的是入队(同步队列)之前,非公平是入队(同步队列)之前都有机会获取锁,公平是没有队伍(等待队列)的可以获取锁。入队(同步队列)后就都为有序的了,只有 node 的 prev 是 head 才有机会获取锁。
1.2 重入锁实现
- 线程可以重复获取已经持有的锁。
- 在非公平和公平锁中,都对重入锁进行了实现。
if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true;}1.3 使用 ReentrantLock 场景
场景 1:假如发现该操作已经在执行中则不再执行(有状态执行)
private ReentrantLock lock = new ReentrantLock();if (lock.tryLock()) { //假如已经被 lock,则立即返回 false 不会等待,达到忽略操作的效果。 try { //操作 } finally { lock.unlock(); }}场景 2:假如发现该操作已经在执行,等待一个一个执行(同步执行,相似 synchronized)
private ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁// private ReentrantLock lock = new ReentrantLock(true); //公平锁try { lock.lock(); //假如被其它资源锁定,会在此等待锁释放,达到暂停的效果 //操作} finally { lock.unlock();}- 公平情况下,操作会排一个队按顺序执行,来保证执行顺序。(会消耗更多的时间来排队)
- 不公平情况下,是无序状态允许插队,JVM 会自动计算如何解决更快速来调度插队。(假如不关心顺序,这个速度会更快)
场景 3:假如发现该操作已经在执行,则尝试等待一段时间,等待超时则不执行(尝试等待执行)
try { if (lock.tryLock(5, TimeUnit.SECONDS)) { //假如已经被 lock,尝试等待 5s,看能否可以取得锁,假如 5s 后依然无法取得锁则返回 false 继续执行 try { //操作 } finally { lock.unlock(); } }} catch (InterruptedException e) { e.printStackTrace(); //当前线程被中断时(interrupt),会抛 InterruptedException }场景 4:假如发现该操作已经在执行,等待执行。这时可中断正在进行的操作立刻释放锁继续下一操作。
try { lock.lockInterruptibly(); //操作} catch (InterruptedException e) { e.printStackTrace();} finally { lock.unlock();}- 这种情况主要用于取消某些操作对资源的占用。如:(取消正在同步运行的操作,来防止不正常操作长时间占用造成的阻塞),该操作的开销也很大,一般不建议使用。
场景 5:条件判断。
- 每一个 lock 可以有任意数据的 Condition 对象。
Lock lock = new ReentrantLock();Condition condition = lock.newCondition();lock.lock();try { while(条件判断表达式) { condition.wait(); } // 解决逻辑} finally { lock.unlock();}1.4 ReentrantLock 的方法
| 方法 | 说明 |
|---|---|
| getHoldCount() | 查询当前线程获取此锁的次数,此线程执行 lock 方法的次数。 |
| getQueueLength() | 返回正等待获取此锁的线程预计数,比方启动 10 个线程,1 个线程取得锁,此时返回的是 9。 |
| getWaitQueueLength(Condition condition) | 返回等待与此锁相关的给定条件的线程预计数。比方 10 个线程,用同一个 condition 对象,并且此时这 10 个线程都执行了 condition 对象的 await 方法,那么此时执行此方法返回 10。 |
| hasWaiters(Condition condition) | 查询能否有线程等待与此锁有关的给定条件(condition),对于指定 contidion 对象,有多少线程执行了 condition.await 方法。 |
| hasQueuedThread(Thread thread) | 查询给定线程能否等待获取此锁。 |
| hasQueuedThreads() | 能否有线程等待此锁。 |
| isFair() | 该锁能否公平锁。 |
| isHeldByCurrentThread() | 当前线程能否保持锁锁定,线程的执行 lock 方法的前后分别是 false 和 true。 |
| isLock() | 此锁能否有任意线程占用。 |
| lockInterruptibly() | 假如当前线程未被中断,获取锁。 |
| tryLock() | 尝试取得锁,仅在调用时锁未被线程占用,取得锁。 |
| tryLock(long timeout, TimeUnit unit) | 假如锁在给定等待时间内没有被另一个线程获取,则获取该锁。 |
1.5 tryLock、lock 和 lockInterruptibly 的区别
- tryLock 能取得锁就返回 true,不能就立即返回 false。
- tryLock(long timeout,TimeUnit unit),可以添加时间限制,假如超过该时间段还没取得锁,返回 false。
- lock 能取得锁就返回 true,不能的话一直等待取得锁。
- lock 和 lockInterruptibly,假如两个线程分别执行这两个方法,但此时中断这两个线程,前者不会抛出异常,然后者会抛出异常。
1.6 总结
- ReentrantLock 提供了内置锁(synchronized)相似的功能和内存语义。
- ReentrantLock 提供了包括定时的锁等待、可中断的锁等待、公平性以及实现非块结构的加锁。
- Condition 对线程的等待和唤醒等操作更加灵活,一个 ReentrantLock 可以有多个 Condition 实例,更有扩展性。
- ReentrantLock 需要显示的获取锁,并在 finally 中释放锁。
- JDK 1.8 以前 ReentrantLock 在性能上似乎优于 synchronized,但获取锁的操作不能与特定的栈帧关联起来,而内置锁可以。
- 由于内置锁时 JVM 的内置属性,所以未来更可能提升 synchronized 而不是 ReentrantLock 的性能。
- 例如对线程封闭的锁对象消除优化,通过添加锁粒度来消除内置锁的同步。
说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 【Java 并发笔记】ReentrantLock 相关整理
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 【Java 并发笔记】ReentrantLock 相关整理