这期内容当中小编将会给大家带来有关怎么在JAVA中使用ReentrantLock实现并发,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
结合上面的ReentrantLock类图,ReentrantLock实现了Lock接口,它的内部类Sync继承自AQS,绝大部分使用AQS的子类需要自定义的方法存在Sync中。而ReentrantLock有公平与非公平的区别,即'是否先阻塞就先获取资源',它的主要实现就是FairSync与NonfairSync,后面会从源码角度看看它们的区别。
Sync是ReentrantLock控制同步的基础。它的子类分为了公平与非公平。使用AQS的state代表获取锁的数量
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; /** * Performs {@link Lock#lock}. The main reason for subclassing * is to allow fast path for nonfair version. */ abstract void lock(); ... }
我们可以看出内部类Sync是一个抽象类,继承它的子类(FairSync与NonfairSync)需要实现抽象方法lock。
下面我们先从非公平锁的角度来看看获取资源与释放资源的原理
故事就从就两个变量开始:
// 获取一个非公平的独占锁 /** * public ReentrantLock() { * sync = new ReentrantLock.NonfairSync(); * } */ private Lock lock = new ReentrantLock(); // 获取条件变量 private Condition condition = lock.newCondition();
lock.lock()
public void lock() { sync.lock(); }
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; // 获取资源 final void lock() { // 若此时没有线程获取到资源,直接设置当前线程独占访问资源。 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else // AQS的方法 acquire(1); } protected final boolean tryAcquire(int acquires) { // 实现在父类Sync中 return nonfairTryAcquire(acquires); } }
AQS的acquire
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
// Sync实现的非公平的tryAcquire final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 此时若没有线程获取到资源,当前线程就直接占用该资源 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 若当前线程已经占用了该资源,可以再次获取该资源 ->这个行为就是可重入锁的支撑 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
尝试获取资源的过程是非常简单的,这里再贴一下acquire的流程
lock.unlock();
public void unlock() { // AQS的方法 sync.release(1); }
AQS的release
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
release的流程已经剖析过了,接下来看看tryRelease的实现
protected final boolean tryRelease(int releases) { int c = getState() - releases; // 这里可以看出若没有持有锁,就释放资源,就会报错 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
tryRelease的实现也很简单,这里再贴一下release的流程图
公平锁与非公平锁,即'是否先阻塞就先获取资源', ReentrantLock中公平与否的控制就在tryAcquire中。下面我们看看,公平锁的tryAcquire
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // (2.3.1) // sync queue中是否存在前驱结点 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; } }
区别在代码(2.3.1)
hasQueuedPredecessors
判断当前线程的前面有无其他线程排队;若当前线程在队列头部或者队列为空返回false
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()); }
结合下面的入队代码(enq), 我们分析hasQueuedPredecessors为true的情况:
1.h != t ,表示此时queue不为空; (s = h.next) == null, 表示另一个结点已经运行了下面的步骤(2),还没来得及运行步骤(3)。简言之,就是B线程想要获取锁的同时,A线程获取锁失败刚好在入队(B入队的同时,之前占有的资源的线程,刚好释放资源)
2.h != t 且 (s = h.next) != null,表示此时至少有一个结点在sync queue中;s.thread != Thread.currentThread(),这个情况比较复杂,设想一下有这三个结点 A -> B C, A此时获取到资源,而B此时因为获取资源失败正在sync queue阻塞,C还没有获取资源(还没有执行tryAcquire)。
时刻一:A释放资源成功后(执行tryRelease成功),B此时还没有成功获取资源(C执行s = h.next时,B还在sync queue中且是老二)
时刻二: C此时执行hasQueuedPredecessors,s.thread != Thread.currentThread()成立,此时s.thread表示的是B
private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) // (1) 第一次初始化 tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { // (2) 设置queue的tail t.next = node; // (3) return t; } } } }
上述就是小编为大家分享的怎么在JAVA中使用ReentrantLock实现并发了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。