写点什么

CountDownLatch 源码分析示例,《Android 面试题及解析》分享给大家

用户头像
Android架构
关注
发布于: 13 小时前

//往下翻一下,有分析


if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){


throw new InterruptedException();


}


}


} catch (Throwable t) {


//往下翻一下,有分析


cancelAcquire(node);


throw t;


}


}


/**


  • 设置同步等待队列的头节点,判断当前处理的节点的后继节点是否共享模式的节点,

  • 如果共享模式的节点,propagate 大于 0 或者节点的 waitStatus 为 PROPAGATE

  • 则进行共享模式下的释放资源


*/


private void setHeadAndPropagate(Node node, int propagate) {


Node h = head;


//设置 node 为头节点


setHead(node);


//propagate 大于 0 || 头节点为 null || 头节点的状态为非取消 || 再次获取头节点为 null || 再次获取头节点的状态为非取消


if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {


Node s = node.next;


//后继节点==null 或者是共享模式的节点


if (s == null || s.isShared())


doReleaseShared();//往上翻,上面分析过了


}


}


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {


int ws = pred.waitStatus;


if (ws == Node.SIGNAL)


//前驱节点状态设置成 Node.SIGNAL 成功,等待被 release 调用释放,后继节点可以安全地进入阻塞状态


return true;


if (ws > 0) {


do {


node.prev = pred = pred.prev;


//waitStatus 大于 0,表示前驱节点已经取消


} while (pred.waitStatus > 0);


//找到一个非取消的节点,重新通过 next 引用连接当前共享模式的节点


pred.next = node;


} else {


//前驱节点非取消状态,全部设置为 Node.SIGNAL


pred.compareAndSetWaitStatus(ws, Node.SIGNAL);


}


return false;


}


// 阻塞当前线程,获取并且重置线程的中断标记位


private final boolean parkAndCheckInterrupt() {


//来了来了,关键的方法:阻塞线程的实现,依赖 Unsafe 的 API


LockSupport.park(this);


return Thread.interrupted();


}


我们再看一下cancelAcquire(node)里面做了什么:


// java.util.concurrent.locks.AbstractQueuedSynchronizer


private void cancelAcquire(Node node) {


if (node == null)


return;


//此时节点的线程已经中断取消,置空节点的线程


node.thread = null;


Node pred = node.prev;


//(跳过取消状态的节点)获取当前节点的上一个非取消状态的节点


while (pred.waitStatus > 0)


node.prev = pred = pred.prev;


//保存 node.prev 非取消状态节点的后继节点


Node predNext = pred.next;


//更新当前节点状态=取消


node.waitStatus = Node.CANCELLED;


// 如果当前节点是尾节点,将当前节点的上一个非取消状态的节点设置为尾节点


// 更新失败的话,则进入 else,如果更新成功,将 tail 的后继节点设置为 null


if (node == tail && compareAndSetTail(node, pred)) {


pred.compareAndSetNext(predNext, null);


} else {


int ws;


// 如果当前节点不是 head 的后继节点


// 1:判断当前节点前驱节点的是否为 SIGNAL,


// 2:如果不是,则把前驱节点设置为 SINGAL 看是否成功


// 如果 1 和 2 中有一个为 true,再判断当前节点的线程是否为 null


// 如果上述条件都满足,把当前节点的前驱节点的后继指针指向当前节点的后继节点


if (pred != head &&


((ws = pred.waitStatus) == Node.SIGNAL ||


(ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) &&


pred.thread != null) {


Node next = node.next;


if (next != null && next.waitStatus <= 0)


pred.compareAndSetNext(predNext, next);


} else {


//上述条件不满足,唤醒当前节点的后继节点


unparkSuccessor(node);


}


node.next = node;// help GC


}


}


private void unparkSuccessor(Node node) {


// 获取头节点 waitStatus


int ws = node.waitStatus;


if (ws < 0)


compareAndSetWaitStatus(node, ws, 0);


// 获取当前节点的下一个节点


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


Node s = node.next;


// 如果下个节点是 null 或者下个节点被 cancelled,就找到队列最开始的非 cancelled 的节点


if (s == null || s.waitStatus > 0) {


s = null;


// 就从尾部节点开始找,到队首,找到队列第一个 waitStatus<0 的节点。


for (Node t = tail; t != null && t != node; t = t.prev)


if (t.waitStatus <= 0)


s = t;


}


// 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点 unpark


if (s != null)


LockSupport.unpark(s.thread);


}


cancelAcquire()?调用的地方:


1.主动中断


2.acquire过程中发生异常


3.超时版本的 API 调用的时候剩余超时时间小于等于零的时候


cancelAcquire()?主要作用是把取消的节点移出同步等待队列,满足上面代码里面分析的条件,会进行后继节点的唤醒unparkSuccessor(node)


5.聊聊 LockSupport 如何实现阻塞和解除阻塞的?



[点击查看 JDK11 LockSupport 文档地址](


)


先看看下面几个代码片段:


//示例一:


Log.d(TAG,"001")


LockSupport.park(this)


Log.d(TAG,"002")




输出:


001


阻塞中.......


//示例二:


LockSupport.unpark(Thread.currentThread())


Log.d(TAG,"001")


LockSupport.park(this)


Log.d(TAG,"002")


....


Log.d(TAG,"执行完")




输出:


001


002


执行完


//示例三:


val thread = Thread.currentThread()


cacheThreadPool.execute{


Log.d(TAG,"一个耗时的异步任务,正在执行...")


Thread.sleep(1500)


//提供许可,解除阻塞


LockSupport.unpark(thread)


}


Log.d(TAG,"001")


//阻塞当前线程


LockSupport.park(this)


Log.d(TAG,"002")


....


Log.d(TAG,"执行完")




输出:


001


一个耗时的异步任务,正在执行...


002


执行完


看了上面的示例,是不是懂了?????


LockSupport park方法有两个:


//java.util.concurrent.locks.LockSupport


public static void park(Object blocker) {


Thread t = Thread.currentThread();


setBlocker(t, blocker);


U.park(false, 0L);


setBlocker(t, null);


}


public static void park() {


U.park(false, 0L);


}


/**


  • 通过反射机制获取 Thread 类的 parkBlocker 字段对象。

  • 然后通过 sun.misc.Unsafe 对象的 objectFieldOffset 方法,

  • 获取到 parkBlocker 在内存里的偏移量


*/


private static void setBlocker(Thread t, Object arg) {


U.putObject(t, PARKBLOCKER, arg);


}


static {


PARKBLOCKER = U.objectFieldOffset


(Thread.class.getDeclaredField("parkBlocker"));


}


用谁?还用疑问??吗?当然推荐大家使用有参数的park(blocker)方法啦。


我们看一下[点击查看 Thread 源码](


)里面的parkBlocker:


/**


  • The argument supplied to the current call to

  • java.util.concurrent.locks.LockSupport.park.

  • Set by (private) java.util.concurrent.locks.LockSupport.setBlocker

  • Accessed using java.util.concurrent.locks.LockSupport.getBlocker


*/


volatile Object parkBlocker;


parkBlocker对象是用来记录线程被阻塞是被谁阻塞的,用于线程监控和分析工具来定位原因的。


LockSupport 通过getBlocker获取到阻塞的对象,主要用于监控和分析线程。




park 阻塞、unpark 解除阻塞,最终会调用UnSafe内部对应的native方法的实现


[点击查看 UnSafe 源码](


)

三、使用场景

1.ARouter 加载指定包名下 class 集合的用法


//com.alibaba.android.arouter.utils.ClassUtils


fun getFileNameByPackageName():Set<String>{


val classNames: Set<String> = HashSet()


val paths = getSourcePaths(context)


val parserCtl = CountDownLatch(paths.size())


for (path in paths) {


DefaultPoolExecutor.getInstance().execute{


try{


//耗时的加载数据....


}finnaly{


parserCtl.countDown()


}


}


}


parserCtl.await()


return classNames


}


2.假设有三个线程:A/B/C,我们在 A/B 完成或者部分完成的时候启动 C


class TaskThread(private val taskName:String, private val countDownLatch: CountDownLatch, private val testSleep:Long): Thread() {


override fun run() {


Log.d(TAG,"『开始』执行{currentThread().name}")


sleep(testSleep)


Log.d(TAG,"【结束】执行{currentThread().name}")

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
CountDownLatch 源码分析示例,《Android面试题及解析》分享给大家