AQS源码深入分析之独占模式-ReentrantLock锁特性详解 (2)

这里需要注意的一点是,CLH队列中的head指针永远会指向一个空节点。如果当前节点被剔除掉,而后面的节点变成第一个节点的时候,此时就会清空该节点里面的内容(waitStatus不会被清除),将head指针指向它。这样做的目的是为了方便进行判断。

2 ReentrantLock概览

独占模式就是只有一个线程能获取到锁资源,独占模式用ReentrantLock来举例,ReentrantLock内部使用sync来继承AQS,有公平锁和非公平锁两种:

1 public class ReentrantLock implements Lock, Serializable { 2 3 //... 4 5 /** 6 * 内部调用AQS 7 */ 8 private final Sync sync; 9 10 /** 11 * 继承AQS的同步基础类 12 */ 13 abstract static class Sync extends AbstractQueuedSynchronizer { 14 //... 15 } 16 17 /** 18 * 非公平锁 19 */ 20 static final class NonfairSync extends Sync { 21 //... 22 } 23 24 /** 25 * 公平锁 26 */ 27 static final class FairSync extends Sync { 28 //... 29 } 30 31 /** 32 * 默认创建非公平锁对象 33 */ 34 public ReentrantLock() { 35 sync = new NonfairSync(); 36 } 37 38 /** 39 * 创建公平锁或者非公平锁 40 */ 41 public ReentrantLock(boolean fair) { 42 sync = fair ? new FairSync() : new NonfairSync(); 43 } 44 45 //... 46 }

公平与非公平锁区别

AQS设计了队列给所有未获取到锁的线程进行排队,那为什么选择队列而不使用Set或者List结构呢?因为队列具有FIFO先入先出特性,即天然具备公平特性,因此在ReentrantLock里才有公平与非公平这两种特性存在。

3 非公平锁 3.1 lock方法

ReentrantLock的非公平锁方式下的lock方法:

1 /** 2 * ReentrantLock: 3 */ 4 public void lock() { 5 sync.lock(); 6 } 7 8 final void lock() { 9 /* 10 首先直接尝试CAS方式加锁,如果成功了,就将exclusiveOwnerThread设置为当前线程 11 这也就是非公平锁的含义,每一个线程在进行加锁的时候,会首先尝试加锁,如果成功了, 12 就不用放在CLH队列中进行排队阻塞了 13 */ 14 if (compareAndSetState(0, 1)) 15 setExclusiveOwnerThread(Thread.currentThread()); 16 else 17 //否则失败的话就进CLH队列中进行阻塞 18 acquire(1); 19 } 3.2 acquire方法

在上面的lock方法中,如果加锁失败了,就会进入到acquire方法中进行排队。但首先还是会尝试获取一次资源:

1 /** 2 * AbstractQueuedSynchronizer: 3 */ 4 public final void acquire(int arg) { 5 //首先尝试获取资源,如果失败了的话就添加一个新的独占节点,插入到CLH队列尾部 6 if (!tryAcquire(arg) && 7 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 8 /* 9 因为本方法不是响应中断的,所以如果当前线程中断后被唤醒,就在此处继续将中断标志位重新置为true 10 (selfInterrupt方法内部就一句话:“Thread.currentThread().interrupt();”),而不是会抛异常 11 (需要使用者在调用lock方法后首先通过isInterrupted方法去进行判断,是否应该执行接下来的业务代码) 12 */ 13 selfInterrupt(); 14 } 15 16 /** 17 * ReentrantLock: 18 * 第6行代码处: 19 * 尝试获取资源 20 */ 21 protected final boolean tryAcquire(int acquires) { 22 return nonfairTryAcquire(acquires); 23 } 24 25 final boolean nonfairTryAcquire(int acquires) { 26 //acquires = 1 27 final Thread current = Thread.currentThread(); 28 int c = getState(); 29 //如果当前没有加锁的话 30 if (c == 0) { 31 //尝试CAS方式去修改state为1 32 if (compareAndSetState(0, acquires)) { 33 //设置当前独占锁拥有者为当前线程 34 setExclusiveOwnerThread(current); 35 return true; 36 } 37 } 38 //当前state不为0,则判断当前线程是否是之前加上锁的线程 39 else if (current == getExclusiveOwnerThread()) { 40 //如果是的话,说明此时是可重入锁,将state+1 41 int nextc = c + acquires; 42 //如果+1之后为负数,说明此时数据溢出了,抛出Error 43 if (nextc < 0) 44 throw new Error("Maximum lock count exceeded"); 45 setState(nextc); 46 return true; 47 } 48 return false; 49 } 3.3 addWaiter方法

如果tryAcquire方法还是获取不到资源,就会调用addWaiter方法来在CLH队列中新增一个节点:

1 /** 2 * AbstractQueuedSynchronizer: 3 * 在CLH队列中添加一个新的独占尾节点 4 */ 5 private Node addWaiter(Node mode) { 6 //把当前线程构建为一个新的节点 7 Node node = new Node(Thread.currentThread(), mode); 8 Node pred = tail; 9 //判断当前尾节点是否为null?不为null说明此时队列中有节点 10 if (pred != null) { 11 //把当前节点用尾插的方式来插入 12 node.prev = pred; 13 //CAS的方式将尾节点指向当前节点 14 if (compareAndSetTail(pred, node)) { 15 pred.next = node; 16 return node; 17 } 18 } 19 //如果队列为空,将队列初始化后插入当前节点 20 enq(node); 21 return node; 22 } 23 24 private Node enq(final Node node) { 25 /* 26 高并发情景下会有很多的CAS失败操作,而下面的死循环确保节点一定要插进队列中。上面的代码和 27 enq方法中的代码是类似的,也就是说上面操作是为了做快速修改,如果失败了,在enq方法中做兜底 28 */ 29 for (; ;) { 30 Node t = tail; 31 //如果尾节点为null,说明此时CLH队列为空,需要初始化队列 32 if (t == null) { 33 //创建一个空的Node节点,并将头节点CAS指向它 34 if (compareAndSetHead(new Node())) 35 //同时将尾节点也指向这个新的节点 36 tail = head; 37 } else { 38 //如果CLH队列此时不为空,则像之前一样用尾插的方式插入该节点 39 node.prev = t; 40 if (compareAndSetTail(t, node)) { 41 t.next = node; 42 return t; 43 } 44 } 45 } 46 } 3.4 acquireQueued方法

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfypy.html