首页 文章 精选 留言 我的

精选列表

搜索[java],共10000篇文章
优秀的个人博客,低调大师

Java并发编程之ThreadLocal源码分析

ThreadLocal介绍 ThreadLocal是JDK1.2提供的,作用是给单个线程内的共享变量提供载具。每个线程之间的ThreadLocal里的数据是相互隔离的,并随着线程的消亡而消亡。 使用 ThreadLocal提供了get(),set(T value),remove()3个对外方法。 1.调用get()获取值 2.调用set(T value)设置值 3.调用remove()删除值 使用中的坑 ThreadLocal常被用来做登入状态信息的存储。但是如果当前线程操作完不对状态信息做remove()可能会出现坑。我们拿购买商品举个例子: A用户已经登入,请求购买 ThreadLocal存储A用户信息。 获取ThreadLocal里A用户信息调用去请求购买接口,并返回成功。 A用户线程未被系统回收,待重复利用。 B用户也发起请求购买请求,并重用了A用户使用过的线程,此时B用户并未登入,所以跳过ThreadLocal存储B用户信息的逻辑。 正常情况下B用户会返回需要登入的提示,但此时ThreadLocal存储A用户信息并未被清除,获取A用户信息并调用去请求购买接口,并返回成功。 可以看到B用户使用了A用户的信息去购买了商品,正确的做法应该是每个线程使用结束后去remove()。 ThreadLocal原理 ThreadLocal的UML图如下 调用set方法真正的数据是存在ThreadLocalMap里的,而ThreadLocalMap是线程Thread的成员变量,所以说线程Thread被jvm回收后ThreadLocalMap也会被回收。ThreadLocalMap的实现是采用顺序存储结构哈希表,它跟HashMap不同,每个hash地址只能存一个数据。它key存的是ThreadLocal本身而且它的Entry继承至WeakReference,所以它的key如果没被强引用会在GC的触发的时候回收掉。 set(T value)源码分析 public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //根据当前线程获取ThreadLocalMap ThreadLocalMap map = getMap(t); //如果map不为空设置值 if (map != null) map.set(this, value); //如果map为空说明线程中成员变量ThreadLocalMap还没被创建,则创建map else createMap(t, value); } 这个方法主要根据当前线程获取ThreadLocalMap,如果还没初始化则调用createMap(t, value)初始化,反之调用map.set(this, value)设置值。 下面看下getMap(t)的实现 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } 很简单就是获取Thread的成员变量threadLocals 先来看下map为空调用createMap(t, value)去创建ThreadLocalMap的情况: void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } createMap(t, value)直接是new的ThreadLocalMap,ThreadLocalMap构造方法如下: ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //1.创建和初始化table容量 table = new Entry[INITIAL_CAPACITY]; //2.快速hash获取下标地址 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //3.创建Entry,存放第一个数据 table[i] = new Entry(firstKey, firstValue); //4.设置存储个数 size = 1; //5.设置扩容阀值 setThreshold(INITIAL_CAPACITY); } 1.先创建和初始化table容量,table就是存放数据的容器,容器初始容量为16。 2.使用快速hash获取下标地址,这里看下获取firstKey.threadLocalHashCode的代码: private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } nextHashCode()的方法每次创建ThreadLocal都会加HASH_INCREMENT重新计算threadLocalHashCode的值,HASH_INCREMENT这个魔数的选取与斐波那契散列有关为了让哈希码能均匀的分布在2的N次方的数组里,这里指table数组。 3.创建Entry,存放第一个数据,这里以ThreadLocal自己本身key,Entry继承至WeakReference,代码如下: static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } 由于key是弱引用,所以它的key如果没被强引用会在GC的触发的时候回收掉。 4.设置长度为1。 5.设置扩容阀值,看下实现 private void setThreshold(int len) { threshold = len * 2 / 3; } 扩容阀值的计算是容量大小的2/3是,这里结果是10。 下面看下map.set(this, value)实现 private void set(ThreadLocal<?> key, Object value) { //调用set之前已经做过判断,所以table已经初始化了 Entry[] tab = table; //获取tab的长度 int len = tab.length; //1.快速hash获取下标地址 int i = key.threadLocalHashCode & (len-1); //2.用线性探测法解决冲突 for (Entry e = tab[i]; e != null; //取下个下标值 e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //3.如果这个key已经存在,重新设置值 if (k == key) { e.value = value; return; } //4.如果key已经过期,则替换这个脏槽 if (k == null) { replaceStaleEntry(key, value, i); return; } } //5.创建Entry tab[i] = new Entry(key, value); //6.存储个数加1 int sz = ++size; //7.清理key已经过期清理的脏槽,如果没脏槽并且存储个数已经大于扩容阀值,则扩容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } 1.快速hash获取下标地址。 2.用线性探测法解决冲突,调用nextIndex(i, len)遍历table。我们看下nextIndex(i, len)的源码 private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } 遍历的实现其实设计了一个环,从i开始遍历到达len长度后又开始从0开始。实际上这里用线性探测法解决冲突不会到达len长度,因为在到达之前已经进行了扩容。 3.如果找到的这个key已经存在,重新设置值。 4.如果找到的key已经过期,则替换这个脏槽。 5.创建Entry。 6.存储个数加1。 7.清理key已经过期清理的脏槽,如果未清理到脏槽并且存储个数已经大于扩容阀值,则调用rehash()重hash。 下面来看下replaceStaleEntry(key, value, i)的源码 private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; int slotToExpunge = staleSlot; //1.向前查找,找到第一个key过期的脏槽 for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; //2.从staleSlot位置开始向后查找,如果找到key,交换至staleSlot位置的脏槽 for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); //找到key if (k == key) { //交换至staleSlot位置的脏槽 e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; //如果slotToExpunge == staleSlot,说明前面没有脏槽,直接从i位置开始清理 if (slotToExpunge == staleSlot) slotToExpunge = i; //清理脏槽 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } //如果slotToExpunge == staleSlot,说明前面没有脏槽,直接从i位置开始清理 if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } //3.如果没有找到key,则创建一个新的Entry放至staleSlot位置的脏槽 tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); //4.如果运行过程中有找到脏槽,清理之 if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } 1.从staleSlot位置向前查找,找到第一个key过期的脏槽 2.从staleSlot位置开始向后查找,如果找到key,交换至staleSlot位置的脏槽 3.如果没有找到key,则创建一个新的Entry放至staleSlot位置的脏槽 4.如果运行过程中有找到脏槽,清理之,这里slotToExpunge != staleSlot 成立说明slotToExpunge已经改变说明找到了脏槽。 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len)的作用主要是清理脏槽expungeStaleEntry(slotToExpunge)方法作用的从slotToExpunge位置(包括slotToExpunge)开始清理临近的脏槽。 下面来看下expungeStaleEntry(slotToExpunge)的源码 private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // 1.清理槽 tab[staleSlot].value = null; tab[staleSlot] = null; //存储个数减一 size--; // 2.重hash或清理staleSlot之后的槽,直到空值 Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); //2.1如果轮询到的k为空,则清理之 if (k == null) { e.value = null; tab[i] = null; size--; } else { //2.2重hash,重新设置hash已经改变的Entry的位置 int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } //3.返回清理遍历的最后位置i return i; } 1.清理staleSlot位置的槽。这里清理的逻辑就是将value设置为null并将整个Entry设置为null,以便后续新Entry覆盖使用。 2.重hash或清理staleSlot之后的槽,直到空值。 2.1 如果轮询到的k为空,则清理之。 2.2 重hash,重新设置hash已经改变的Entry的位置。这里i地址有可能是经过线性探测解决的冲突的方式找到的地址,因为前面的槽已经被清理过所以线性探测解决的冲突方法找到的地址可能已经不是i,所以这边需要重新用线性探测解决的冲突方法查找新地址。 3 .返回清理遍历的最后位置i。 总结下expungeStaleEntry(slotToExpunge)逻辑其实不仅仅清理传的slotToExpunge地址的槽也会清理它临近的槽。 拿到清理遍历的最后位置i后会调用cleanSomeSlots(int i, int n)继续从i开始清理脏槽下面来看下的源码: private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { //环形遍历 i = nextIndex(i, len); Entry e = tab[i]; //如果是脏槽,则清理之 if (e != null && e.get() == null) { n = len; removed = true; //最终调用expungeStaleEntry(i)去清理 i = expungeStaleEntry(i); } //log2(n)清理次数 } while ( (n >>>= 1) != 0); return removed; } cleanSomeSlots(int i, int n)主要功能就是从i位置开始遍历log2(n)次去清理槽,为什么是log2(n)次官方给的原因是简单,快速。所以这个方法可能不是清理所有的脏槽,而是简单快速的清理几个脏槽。 下面来看下rehash()方法 private void rehash() { //1.清理所有的脏槽 expungeStaleEntries(); //2.如果清理过后存储个数还是大于扩容阀值的3/4,则扩容 if (size >= threshold - threshold / 4) resize(); } 1.调用expungeStaleEntries()清理所有的脏槽。 2.如果清理过后存储个数还是大于扩容阀值的3/4,则扩容。 下面看下expungeStaleEntries()方法源码 private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } } 代码很简单就是遍历所有的table并清理脏槽。 下面看下resize()方法源码 private void resize() { Entry[] oldTab = table; //获取老table容量 int oldLen = oldTab.length; //新table容量扩大2倍 int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; //遍历老的table,对所有Entry重hash定位 for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal<?> k = e.get(); //如果遇到脏槽,清理之帮助GC if (k == null) { e.value = null; // Help the GC } else { //重hash int h = k.threadLocalHashCode & (newLen - 1); //线性探测法解决冲突 while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } //重新计算扩容阀值 setThreshold(newLen); //重新设置存储个数 size = count; //重新设置table table = newTab; } resize()实现也比较简单,先创建比原来大2倍的Entry数组,并遍历老的table,对所有Entry重hash定位,如果冲突就是采用线性探测法解决冲突。 get()源码分析 看下get()的源码 public T get() { //获取当前线程 Thread t = Thread.currentThread(); //根据当前线程获取ThreadLocalMap ThreadLocalMap map = getMap(t); //如果不为空获取Entry if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //为空获取初始化的值 return setInitialValue(); } 这个方法主要根据当前线程获取ThreadLocalMap,如果还没初始化则调用setInitialValue()初始化并返回值,反之调用map.getEntry(this)获取值。 先来看下map不为空调用map.getEntry(this)的源码: private Entry getEntry(ThreadLocal<?> key) { //1.快速hash获取hash地址 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; //2.如果找到Entry,则返回 if (e != null && e.get() == key) return e; //3.如果未快速找到,则去遍历查找 else return getEntryAfterMiss(key, i, e); } 1.快速hash获取hash地址 2.如果找到Entry,则返回 3.如果未快速找到,则调用getEntryAfterMiss(key, i, e)去遍历查找,由于用线性探测法解决冲突, 来看下getEntryAfterMiss(key, i, e)的源码: private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; //从i位置开始遍历table while (e != null) { ThreadLocal<?> k = e.get(); //如果找到直接返回 if (k == key) return e; //如果是脏槽清理之 if (k == null) expungeStaleEntry(i); //获取下个地址 else i = nextIndex(i, len); e = tab[i]; } return null; } 实现很简单就是从i位置开始遍历table,找到就返回Entry,遍历过程中顺便清理脏槽。 再来看下setInitialValue()的源码: private T setInitialValue() { //1.获取默认初始化值 T value = initialValue(); Thread t = Thread.currentThread(); //2.根据当前线程获取ThreadLocalMap ThreadLocalMap map = getMap(t); //3.不为空,设置值 if (map != null) map.set(this, value); //4.反之初始化map else createMap(t, value); return value; } 1.获取默认初始化值,这里initialValue()是默认返回null的,源码如下: protected T initialValue() { return null; } 这个可以自己实现覆盖原来的方法。 2.根据当前线程获取ThreadLocalMap。 3.不为空,则调用map.set(this, value)设置值。 4.反之则调用createMap(t, value)初始化map。 remove()源码分析 直接看下remove()源码 public void remove() { //1.根据当前线程获取ThreadLocalMap ThreadLocalMap m = getMap(Thread.currentThread()); //2.如果map已经存在则调用m.remove(this)删除值 if (m != null) m.remove(this); } 1.根据当前线程获取ThreadLocalMap 2.如果map已经存在则调用m.remove(this)删除key为本身的Entry 下面来看下m.remove(this)的源码: private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; //快速hash到地址 int i = key.threadLocalHashCode & (len-1); //向后查找 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //如果找到 if (e.get() == key) { //清理key e.clear(); //清理脏槽 expungeStaleEntry(i); return; } } } 实现很简单,先快速hash到地址i,然后从这个地址i往后查找key(包括地址i)直到槽为空,如果找到则清理之。

优秀的个人博客,低调大师

Java并发编程之Condition源码分析

Condition介绍 上篇文章讲了ReentrantLock的加锁和释放锁的使用,这篇文章是对ReentrantLock的补充。ReentrantLock#newCondition()可以创建Condition,在ReentrantLock加锁过程中可以利用Condition阻塞当前线程并临时释放锁,待另外线程获取到锁并在逻辑后通知阻塞线程"激活"。Condition常用在基于异步通信的同步机制实现中,比如dubbo中的请求和获取应答结果的实现。 常用方法 Condition中主要的方法有2个 (1)await()方法可以阻塞当前线程,并释放锁。 (2)在获取锁后可以调用signal()通知被await()阻塞的线程"激活"。 这里的await(),signal()必须在ReentrantLock#lock()和ReentrantLock#unlock()之间调用。 Condition实现分析 Condition的实现也是利用AbstractQueuedSynchronizer队列来实现,await()在被调用后先将当前线程加入到等待队列中,然后释放锁,最后阻塞当前线程。signal()在被调用后会先获取等待队列中第一个节点,并将这个节点转化成ReentrantLock中的节点并加入到同步阻塞队列的结尾,这样此节点的上个节点线程释放锁后会激活此节点线程取来获取锁。 await()方法源码分析 await()源码如下 public final void await() throws InterruptedException { //判断是否当前线程是否被中断中断则抛出中断异常 if (Thread.interrupted()) throw new InterruptedException(); //加入等待队列 Node node = addConditionWaiter(); //释放当前线程锁 int savedState = fullyRelease(node); int interruptMode = 0; //判断是否在同步阻塞队列,如果不在一直循环到被加入 while (!isOnSyncQueue(node)) { //阻塞当前线程 LockSupport.park(this); //判断是否被中断 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } //获取锁,如果获取中被中断则设置中断状态 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; //清除等待队列中被"激活"的节点 if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); //如果当前线程被中断,处理中断逻辑 if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } 主要分以下几步 (1)先判断是否当前线程是否被中断中断则抛出中断异常如果未中断调用addConditionWaiter()加入等待队列 (2)调用fullyRelease(node)释放锁使同步阻塞队列的下个节点线程能获取锁。 (3)调用isOnSyncQueue(node)判断是否在同步阻塞队列,这里的加入同步阻塞队列操作是在另一个线程调用signal()后加入,如果不在同步阻塞队列会进行阻塞直到被激活。 (4)如果被激活然后调用checkInterruptWhileWaiting(node)判断是否被中断并获取中断模式。 (5)继续调用isOnSyncQueue(node)判断是否在同步阻塞队列。 (6)是则调用acquireQueued(node, savedState) 获取锁,这里如果获取不到也会被阻塞,获取不到原因是在第一次调用isOnSyncQueue(node)前,可能另一个线程已经调用signal()后加入到同步阻塞队列,然后调用acquireQueued(node, savedState) 获取不到锁并阻塞。acquireQueued(node, savedState)也会返回当前线程是否被中断,如果被中断设置中断模式。 (7)在激活后调用unlinkCancelledWaiters()清理等待队列的已经被激活的节点。 (8)最后判断当前线程是否被中断,如果被中断则对中断线程做处理。 下面来看下addConditionWaiter()实现 private Node addConditionWaiter() { //获取等待队列尾部节点 Node t = lastWaiter; //如果尾部状态不为CONDITION,如果已经被"激活",清理之,然后重新获取尾部节点 if (t != null && t.waitStatus != Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; } //创建以当前线程为基础的节点,并将节点模式设置成CONDITION Node node = new Node(Thread.currentThread(), Node.CONDITION); //如果尾节点不存在,说明队列为空,将头节点设置成当前节点 if (t == null) firstWaiter = node; //如果尾节点存在,将此节点设置成尾节点的下个节点 else t.nextWaiter = node; //将尾节点设置成当前节点 lastWaiter = node; return node; } addConditionWaiter()的逻辑很简单,就是创建以当前线程为基础的节点并把节点加入等待队列的尾部待其他线程处理。 下面来看下fullyRelease(Node node)实现 final int fullyRelease(Node node) { boolean failed = true; try { //获取阻塞队列中当前线程节点的锁状态值 int savedState = getState(); //释放当前线程节点锁 if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { //释放失败讲节点等待状态设置成关闭 if (failed) node.waitStatus = Node.CANCELLED; } } 调用getState()先获取阻塞队列中当前线程节点的锁状态值,这个值可能大于1表示多次重入,然后调用release(savedState)释放所有锁,如果释放成功返回锁状态值。 下面来看下isOnSyncQueue(Node node)实现 final boolean isOnSyncQueue(Node node) { //判断当前节点是否是CONDITION或者前置节点是否为空如果为空直接返回false if (node.waitStatus == Node.CONDITION || node.prev == null) return false; //如果下个节点存在,则在同步阻塞队列中返回true if (node.next != null) // If has successor, it must be on queue return true; //遍历查找当前节点是否在同步阻塞队列中 return findNodeFromTail(node); } private boolean findNodeFromTail(Node node) { Node t = tail; for (;;) { if (t == node) return true; if (t == null) return false; t = t.prev; } } 此方法的功能是查找当前节点是否在同步阻塞队列中,方法先是快速判断,判断不了再进行遍历查找。 (1)第一步先判断次节点是否CONDITION状态或者前置节点是否存在,如果是表明不在队列中返回false,阻塞队列中的状态一般是0或者SIGNAL状态而且如果当前如果当前节点在队列阻塞中且未被激活前置节点一定不为空。 (2)第二步判断节点的下个节点是否存在,如果存在则表明当前当前节点已加入到阻塞队列中。 (3)如果以上2点都没法判断,也有可能刚刚加入到同步阻塞队列中,所以调用findNodeFromTail(Node node)做最后的遍历查找。查找从队列尾部开始查,从尾部开始查的原因是可能刚刚加入到同步阻塞队列中,从尾部能快速定位。 下面看下checkInterruptWhileWaiting(Node node)实现 private int checkInterruptWhileWaiting(Node node) { return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; } final boolean transferAfterCancelledWait(Node node) { if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { enq(node); return true; } while (!isOnSyncQueue(node)) Thread.yield(); return false; } 此方法在线程被激活后被调用,主要功能就是判断被激活的线程是否被中断。此方法会返回2种中断状态THROW_IE和REINTERRUPT,THROW_IE是调用signal()前被中断返回,REINTERRUPT在调用signal()后被中断返回。 此方法先判断是否被标记中断,是的话再调用transferAfterCancelledWait(node)取判断是那种中断状态,transferAfterCancelledWait(node)方法分2步 (1)用CAS方式将节点状态改错等待状态改成CONDITION,并加入到同步阻塞队列中返回true (2)如果不能加入到同步阻塞队列就自旋一直等待加入 如果使用await()方法上面2步其实是没什么作用其最后一定会返回false,因为await()被激活只能调用 signal()方法,而signal()方法肯定已经将节点加入到同步阻塞队列中。所以以上逻辑是给await(long time, TimeUnit unit)等带超时激活方法用的。 acquireQueued(node, savedState)方法再上一章节已经讲过这边就不重复了,下面分析下unlinkCancelledWaiters()方法 private void unlinkCancelledWaiters() { //获取等待队列头节点 Node t = firstWaiter; Node trail = null; while (t != null) { //获取下个节点 Node next = t.nextWaiter; //如果状态不为CONDITION说明已经加入阻塞队列需要清理掉 if (t.waitStatus != Node.CONDITION) { t.nextWaiter = null; if (trail == null) firstWaiter = next; else //获取下个节点 trail.nextWaiter = next; if (next == null) lastWaiter = trail; } else trail = t; t = next; } } 此方法就是从头开始查找状态不为CONDITION的节点并清理,状态不为CONDITION节点说明此节点已经加入到阻塞队列,已经不需要维护。 下面来看下reportInterruptAfterWait(interruptMode)方法 private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { //如果是THROW_IE模式直接抛出异常 if (interruptMode == THROW_IE) throw new InterruptedException(); //如果是REINTERRUPT模式标记线程中断由上层处理中断 else if (interruptMode == REINTERRUPT) selfInterrupt(); } 此方法处理中断逻辑。如果是THROW_IE模式直接抛出异常,如果是REINTERRUPT模式标记线程中断由上层处理中断。 signal()方法源码分析 signal()源码如下 public final void signal() { //是否当前线程持有锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; //通知"激活"头节点线程 if (first != null) doSignal(first); } 先调用isHeldExclusively()判断锁是否被当前线程持有,然后检查等待队列是否为空,不为空就是可以取第一个节点调用doSignal(first)去"激活",这里激活不是真正的激活而只是将节点加入到同步阻塞队列尾部,所以上下文中带""的激活都是这种解释。 下面看下isHeldExclusively()实现 protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } 实现就是比较下当前线程和持有锁的线程是否同一个 下面看下doSignal(first)的实现 private void doSignal(Node first) { do { //头指头后移一位,如果后面的节点为空,则将尾指头也指向空,说明队列为空了 if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; //清空头节点的下个节点 first.nextWaiter = null; //如果"激活"失败者取下个继续,直到成功或者遍历完 } while (!transferForSignal(first) && (first = firstWaiter) != null); } 此方法就是取当前头节点一直去尝试"激活",直到成功或者遍历完。 下面来看下transferForSignal(first)方法 final boolean transferForSignal(Node node) { //将CONDITION状态设置成0 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; //加入到同步阻塞队列 Node p = enq(node); int ws = p.waitStatus; //状态异常直接激活 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; } (1)此方法先先将CONDITION状态设置成0,因为如果是CONDITION状态加入到同步阻塞队列,激活的时候是不识别的。 (2)加入到同步阻塞队列的尾部。所以同步阻塞队列中前面如果有多个在排队,调用unlock()不会马上激活此节点。 (3)状态异常直接调用unpark激活,这边按理说如果状态异常情况下激活,await()在调用unlock()被激活后会进行相应的异常处理,但看await()代码没有处理则是正常执行。 这个方法主要就是把节点加入到同步阻塞队列的,真正的激活则是调用unlock()去处理。

优秀的个人博客,低调大师

Java-override重写与overload重载

重载(overload) 在同一个类中,方法名相同,参数列表不同(参数个数,参数顺序,参数类型) 仅仅返回类型不同不足以称为方法的重载(重载与方法返回类型无关) 仅仅参数变量名称不同是不可以的 重写(override) 子类继承父类,方法名相同,参数列表相同,权限修饰符作用范围要大于父类的范围 父类用public修饰的方法被重写,子类中方法的修饰符只能是public 静态方法不能被重写

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册