前言
在近期的测试中遇到了一个Java.lang.InterruptedException
的异常,StackTrace如下:
java.lang.InterruptedException at java.base/java.lang.Object.wait(Native Method) at java.base/java.lang.Object.wait(Object.java:328) at bt.torrent.messaging.MetadataConsumer.waitForTorrent(MetadataConsumer.java:265) at bt.processor.magnet.FetchMetadataStage.doExecute(FetchMetadataStage.java:92) at bt.processor.magnet.FetchMetadataStage.doExecute(FetchMetadataStage.java:42) at bt.processor.TerminateOnErrorProcessingStage.doExecute(TerminateOnErrorProcessingStage.java:38) at bt.processor.RoutingProcessingStage.execute(RoutingProcessingStage.java:39) at bt.processor.ChainProcessor.doExecute(ChainProcessor.java:112) at bt.processor.ChainProcessor.executeStage(ChainProcessor.java:96) at bt.processor.ChainProcessor.executeStage(ChainProcessor.java:98) at bt.processor.ChainProcessor.lambda$process$0(ChainProcessor.java:81) at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run$$$capture(CompletableFuture.java:1736) at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:834)
debug定位到抛出异常的源码如下:
其中torrent.wait();
的torrent
的定义为private final AtomicReference<Torrent> torrent;
。因此,本文将结合此次问题的排查过程,深入理解AtomicReference
的方方面面。在文章最后也会将遇到的问题排查过程进行一个简短的分析和总结。
希望能够帮助大家更好的理解AtomicReference
。
原子性
介绍AtomicReference
之前,首先需要理解的一个关键概念:原子性。让我们先来看一个简单的例子:
当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值,比如变量i=1
,A线程更新i+1
,B线程也更新i+1
,经过两个线程操作之后可能i并不等于3,而是等于2。
因为A和B线程在更新变量i的时候拿到的i都是1,这就是线程不安全的更新操作,一般情况下我们会使用synchronized
来解决这个问题,synchronized
会保证多线程不会同时更新变量i。
从JDK1.5开始Java提供了java.util.concurrent.atomic
包,在这个包中为原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方法。本文中着重介绍的AtomicReference
就是其中一个典型的代表。
AtomicReference
首先看下 JDK 中对AtomicReference
的定义:
An object reference that may be updated atomically. See the java.util.concurrent.atomic package specification for description of the properties of atomic variables.
JavaDoc:https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
AtomicReference
的作用是对对象
进行原子操作
。它提供了一种读和写都是原子性
的对象引用变量。
同时,原子性
意味着多个线程试图改变同一个 AtomicReference(例如比较和交换操作)将不会使得 AtomicReference 处于不一致的状态。
atomic 包
提到AtomicReference
就一定会提到原子包:java.util.concurrent.atomic
,在这里 JDK 提供了众多的针对原子操作的类。包括:
Number
原子地更新一个对象的long类型的field,这个long类型的field必须是被volatile修饰的;
基于反射的实用程序,可以对指定类的指定volatile long字段进行原子更新。 此类设计用于原子数据结构,其中同一节点的多个字段独立地受原子更新的影响。
请注意, compareAndSet方法的保证比其他原子类弱。 由于此类无法确保该字段的所有使用都适用于原子访问,因此只能在同一更新程序上对compareAndSet和set其他调用保证原子性。
原子地更新一个对象的int类型的field,这个int类型的field必须是被volatile修饰的;
基于反射的实用程序,可以对指定类的指定volatile int字段进行原子更新。 此类设计用于原子数据结构,其中同一节点的多个字段独立地受原子更新的影响。
请注意, compareAndSet方法的保证比其他原子类弱。 因为此类无法确保该字段的所有使用都适用于原子访问的目的,所以它只能保证在同一更新程序上对compareAndSet和set其他调用的原子性。
使用提供的函数一起维护运行的long值的一个或多个变量。 当跨线程争用更新(方法accumulate(long) )时,变量集可以动态增长以减少争用。 方法get() (或等效地, longValue() )返回维护更新的变量的当前值。
当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共值时,此类通常优于AtomicLong 。 在低更新争用下,这两个类具有相似的特征。 但在高争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
线程内或线程之间的累积顺序无法保证且不能依赖,因此该类仅适用于累积顺序无关紧要的函数。 提供的累加器功能应该是无副作用的,因为当尝试的更新由于线程之间的争用而失败时可以重新应用它。 对于可预测的结果,累加器函数应该是关联的和可交换的。 该函数应用现有值(或标识)作为一个参数,给定更新作为另一个参数。 例如,要保持运行的最大值,您可以提供Long::max以及Long.MIN_VALUE作为标识。
类LongAdder提供了此类功能的类比,用于维护计数和总和的常见特殊情况。 电话new LongAdder()相当于new LongAccumulator((x, y) -> x + y, 0L) 。
使用提供的函数一起维护运行的double值的一个或多个变量。
当更新(方法accumulate(double) )跨线程争用时,变量集可以动态增长以减少争用。
方法get() (或等效地, doubleValue() )返回维护更新的变量的当前值。
当多个线程更新公共值时,此类通常优于备选方案,该公共值用于诸如经常更新但读取频率较低的摘要统计信息之类的目的。
提供的累加器功能应该是无副作用的,因为当尝试的更新由于线程之间的争用而失败时可以重新应用它。
对于可预测的结果,累加器函数应该在使用上下文中所需的浮点容差内是可交换的和关联的。
该函数应用现有值(或标识)作为一个参数,给定更新作为另一个参数。 例如,要保持运行的最大值,您可以提供Double::max以及Double.NEGATIVE_INFINITY作为标识。
线程内或线程之间的累积顺序无法保证。 因此,如果需要数值稳定性,则该类可能不适用,尤其是在组合基本上不同数量级的值时。
一个或多个变量,它们共同维持最初的零long总和。
当跨线程争用更新(方法add(long) )时,变量集可以动态增长以减少争用。
方法sum() (或等效地, longValue() )返回保持总和的变量的当前总和。
当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共和时,此类通常优于AtomicLong 。
在低更新争用下,这两个类具有相似的特征。 但在高争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
一个或多个变量一起保持最初为零的总和为double 。
当跨线程争用更新(方法add(double) )时,变量集可以动态增长以减少争用。
方法sum() (或等效地, doubleValue() )返回保持总和的变量的当前总和。
线程内或线程之间的累积顺序无法保证。
因此,如果需要数值稳定性,则该类可能不适用,尤其是在组合基本上不同数量级的值时。
提供对long类型的原子操作
提供对boolean类型的原子操作
提供对int类型的原子操作,是对基本类型封装的一个代表;
AtomicInteger
AtomicBoolean
AtomicLong
DoubleAdder
LongAdder
Striped64
DoubleAccumulator
LongAccumulator
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
Array
提供对long数组的原子操作,可以原子地更新数组里的某个index上的值;
提供对int数组的原子操作,可以原子地更新数组里的某个index上的值;
AtomicIntegerArray
AtomicLongArray
Reference
与AtomicMarkableReference类似,不过把标志位换成了一个int值,原子的更新reference和int值;
原子更新带有标记位的引用类型。
需要传入一个reference和一个boolean类型的标志位,可以原子地更新reference和标志位;
原子更新引用类型里的字段
原子地更新一个reference类型的数组。
原子更新引用类型,提供对引用类型的原子操作
但是并不是说可以原子地操作引用的对象里的字段,可以将引用原子地指向两一个对象;
AtomicReference
AtomicReferenceArray
AtomicReferenceFieldUpdater
AtomicMarkableReference
AtomicStampedReference
继承自Number
的原子类,如AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用Reference
等原子更新的引用类型提供的类来完成了。
这里只是简单介绍些每个类的作用,关于java.util.concurrent.atomic
包并不是本文的重点,有兴趣的话,可以通过翻阅JDK源码进一步的了解。
源码
package java.util.concurrent.atomic;import java.util.function.UnaryOperator;import java.util.function.BinaryOperator;import sun.misc.Unsafe;public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; // 空歌白石:基于Unsafe实现原子操作 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 空歌白石:基于volatile实现原子操作 private volatile V value; public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } // 空歌白石:基于volatile实现原子操作 public final V get() { return value; } public final void set(V newValue) { value = newValue; } // 空歌白石:基于Unsafe实现原子操作 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } // 空歌白石:基于Unsafe实现原子操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; } public String toString() { return String.valueOf(get()); }}
原子操作
通过AtomicReference
的源码可以看出,AtomicReference
是基于volatile
和sun.misc.Unsafe
来实现对于引用的原子操作的。
volatile
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
禁止进行指令重排序。
sun.misc.Unsafe
通过上文可以看出一点,即java.util.concurrent.atomic
包中的类基本上都是使用sun.misc.Unsafe
实现的包装类,换句话说,java.util.concurrent.atomic
是对sun.misc.Unsafe
的封装。那么sun.misc.Unsafe
就是何方神圣呢?
简单讲一下这个类。Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作。
如果对优秀开源项目有过深入分析研究的同学应该对sun.misc.Unsafe
并不陌生,为了追求极致性能,绝大部分的高性能框架或项目基本上都离不开sun.misc.Unsafe
类,Unsafe
并不是说这个类中的方法不安全,而是说想要使用好sun.misc.Unsafe
并不容易,需要对sun.misc.Unsafe
和底层原理有深刻的理解,因此并不推荐在一般项目中直接使用,这也从一个侧面说明了为何Unsafe
类并不是在java
包下,而是在sun
包下。
这个类尽管里面的方法都是public的,但是并没有办法使用它们,JDK API文档也没有提供任何关于这个类的方法的解释。总而言之,对于Unsafe类的使用都是受限制的,只有授信的代码才能获得该类的实例,当然JDK库里面的类是可以随意使用的。
Unsafe提供了硬件级别的操作,比如说获取某个属性在内存中的位置,比如说修改对象的字段值,即使它是私有的。不过Java本身就是为了屏蔽底层的差异,对于一般的开发而言也很少会有这样的需求。正因为Unsafe的硬件级别的操作,使得Unsafe的性能极高,在追求高性能的场景下使用极为广泛。
CAS
CAS,Compare and Swap
即比较并交换
,设计并发算法时常用到的一种技术,java.util.concurrent
包完全建立在CAS之上,没有CAS也就没有此包,在上文的AtmoicReference
中也可以看到众多的compareAndSwap方法。
此外,当前的处理器基本都支持CAS,只不过不同的厂家的实现不一样罢了。CAS有三个操作数:
内存值V
旧的预期值A
要修改的值B
当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。
使用CAS的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件实现来保证线程安全。相对于其他锁的实现没有现场切换和阻塞,也就没有了额外的开销,并且可以支持较大的并发性。(当然CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。)
AtomicReference的使用
AtomicReference
的初始化过程。
// 空歌白石:在获取和验证元数据后立即设置原子集private final AtomicReference<Torrent> torrent;public MetadataConsumer(IMetadataService metadataService, TorrentId torrentId, Config config, EventSource eventSource) {// 空歌白石:略去无关代码this.torrent = new AtomicReference<>();// 空歌白石:略去无关代码}
以下代码为实际开发中AtomicReference的代码:
private void processMetadataBlock(int pieceIndex, int totalSize, byte[] data) { // 空歌白石:略去无关代码 synchronized (torrent) { torrent.set(fetchedTorrent); states.clear(); torrent.notifyAll(); } // 空歌白石:略去无关代码}@Producespublic void produce(Consumer<Message> messageConsumer, MessageContext context) { // 空歌白石:如果元数据已经被获取,则停止到此。 if (torrent.get() != null) { return; } // 空歌白石:略去无关代码}/*** 空歌白石:如果Torrent还没有被获取,便阻塞调用线程,等待获取到Torrent。*/public Torrent waitForTorrent() { while (torrent.get() == null) { synchronized (torrent) { if (torrent.get() == null) { try { torrent.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } return torrent.get();}
new AtomicReference<>() is null
为更好的展示原理,仅仅将最核心代码编写了一个demo,如下,可以看出AtomicReference的值是null,这是又为什么呢?
原因是以上代码仅仅是将new AtomicReference<>();
,实际上并没有真正将具体的reference传递给AtomicReference,因此,看到的integer的值仍为null。
我们更换一种写法,在创建AtomicReference时,随即将引用对象赋值,代码如下:
// 创建一个对象String str = "testAtomicReference";// 将引用传给AtomicReference对象AtomicReference<String> atomicReference = new AtomicReference<String>(str);// 获取到AtomicReference中的引用对象String value = atomicReference.get();// 创建一个新的对象String newStr = "new testAtomicReference";// compareAndSet方法会比较当前AtomicReference对象中的引用的是否是str;如果是,则会更新为newStr,如果不是,则不会更新。boolean exchanged = atomicReference.compareAndSet(str, newStr);
当new AtomicReference<String>(str);
时,实际只是将str的引用传递给了AtomicReference,因此debug看到的仍然为null,实际在get方法是可以获取到具体的value,通过debug可以看出符合我们的预期,如下图:
java.lang.Object
在上述waitForTorrent
方法中,会使用到torrent.wait()
方法,wait
方法是在Object
类中定义的。对于Object
类相信大家都并不陌生,特别是其中的equals
、hashCode
、toString
等方法,但是如果不涉及到原子性操作或多线程开发,对于wait
、notify
、notifyAll
等方法可能使用的并不多。
package java.lang;import jdk.internal.HotSpotIntrinsicCandidate;public class Object { private static native void registerNatives(); static { registerNatives(); } @HotSpotIntrinsicCandidate public Object() {} @HotSpotIntrinsicCandidate public final native Class<?> getClass(); @HotSpotIntrinsicCandidate public native int hashCode(); public boolean equals(Object obj) { return (this == obj); } @HotSpotIntrinsicCandidate protected native Object clone() throws CloneNotSupportedException; public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } @HotSpotIntrinsicCandidate public final native void notify(); @HotSpotIntrinsicCandidate public final native void notifyAll(); public final void wait() throws InterruptedException { wait(0L); } public final native void wait(long timeoutMillis) throws InterruptedException; public final void wait(long timeoutMillis, int nanos) throws InterruptedException { if (timeoutMillis < 0) { throw new IllegalArgumentException("timeoutMillis value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeoutMillis++; } wait(timeoutMillis); } @Deprecated(since="9") protected void finalize() throws Throwable { }}
wait
这里特别将Object
的wait
方法单独介绍,通过以下源码可以看出,多个重载的wait
方法最终都会调用native
标注的wait(long timeoutMillis)
方法,此方法可能会抛出InterruptedException
中断异常。
public final void wait() throws InterruptedException { wait(0L);}public final void wait(long timeoutMillis, int nanos) throws InterruptedException { if (timeoutMillis < 0) { throw new IllegalArgumentException("timeoutMillis value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeoutMillis++; } wait(timeoutMillis);}public final native void wait(long timeoutMillis) throws InterruptedException;
那么何时会抛出异常呢?
如果任何线程在当前线程等待通知之前或期间中断了当前线程,便抛出此异常时清除当前线程的中断状态。
InterruptedException
本小节着重介绍InterruptedException
,我们从InterruptedException
的源码入手。
package java.lang;/** * Thrown when a thread is waiting, sleeping, or otherwise occupied, * and the thread is interrupted, either before or during the activity. * Occasionally a method may wish to test whether the current * thread has been interrupted, and if so, to immediately throw * this exception. The following code can be used to achieve * this effect: * <pre> * if (Thread.interrupted()) // Clears interrupted status! * throw new InterruptedException(); * </pre> * * @author Frank Yellin * @see java.lang.Object#wait() * @see java.lang.Object#wait(long) * @see java.lang.Object#wait(long, int) * @see java.lang.Thread#sleep(long) * @see java.lang.Thread#interrupt() * @see java.lang.Thread#interrupted() * @since 1.0 */public class InterruptedException extends Exception { private static final long serialVersionUID = 6700697376100628473L; /** * Constructs an <code>InterruptedException</code> with no detail message. */ public InterruptedException() { super(); } /** * Constructs an <code>InterruptedException</code> with the * specified detail message. * * @param s the detail message. */ public InterruptedException(String s) { super(s); }}
InterruptedException
线程堵塞异常,加上上文中已经介绍过的Object的wait方法,这里再列举几个可能会抛出InterrptdeException异常方法:
java.lang.Object.wait()
及其重载方法
会进入等待区等待。
java.lang.Thread.sleep()
及其重载方法
会睡眠执行参数内所设置的时间。
java.lang.Thread.join()
及其重载方法
会等待到指定的线程结束为止。
以上这些方法执行结束后,该线程会重新启动,因此可能会出现线程堵塞。故这些方法需要处理可能抛出的InterrptdeException异常。
Thread.interrupt()
wait()
、sleep()
、join()
等方法都会使得当前线程进入阻塞状态,若另外的一个线程调用被阻塞线程的Thread.interrupt()
,则会打断这种阻塞,抛出InterruptedException
。InterruptedException
就像一个signal(信号)一样通知当前线程被打断了。但是打断一个线程并不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。
问题排查
进一步分析
通过上次对AtomicReference
相关逻辑的理解,回到最初的问题,可以确定一定是某种原因导致中断的发生,表现就是当AtomicReference
调用wait()
方法后引起了InterruptedException
。
经过进一步分析log,发现了新的StackTrace,如下:
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$AsyncSupply@3267902e rejected from java.util.concurrent.ThreadPoolExecutor@d4ab1ed[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0] at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055) at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:825) at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1355) at java.base/java.util.concurrent.CompletableFuture.asyncSupplyStage(CompletableFuture.java:1714) at java.base/java.util.concurrent.CompletableFuture.supplyAsync(CompletableFuture.java:1931) at bt.net.ConnectionSource.getConnectionAsync(ConnectionSource.java:132) at bt.torrent.messaging.TorrentWorker.onPeerDiscovered(TorrentWorker.java:412) at bt.torrent.messaging.TorrentWorker.lambda$new$0(TorrentWorker.java:108) at bt.event.EventBus.lambda$addListener$3(EventBus.java:249) at bt.event.EventBus.doFireEvent(EventBus.java:183) at bt.event.EventBus.fireEvent(EventBus.java:171) at bt.event.EventBus.firePeerDiscovered(EventBus.java:62) at bt.peer.PeerRegistry.addPeer(PeerRegistry.java:214) at bt.processor.magnet.FetchMetadataStage.lambda$doExecute$0(FetchMetadataStage.java:81) at java.base/java.lang.Iterable.forEach(Iterable.java:75) at bt.processor.magnet.FetchMetadataStage.doExecute(FetchMetadataStage.java:80) at bt.processor.magnet.FetchMetadataStage.doExecute(FetchMetadataStage.java:42) at bt.processor.TerminateOnErrorProcessingStage.doExecute(TerminateOnErrorProcessingStage.java:38) at bt.processor.RoutingProcessingStage.execute(RoutingProcessingStage.java:39) at bt.processor.ChainProcessor.doExecute(ChainProcessor.java:112) at bt.processor.ChainProcessor.executeStage(ChainProcessor.java:96) at bt.processor.ChainProcessor.executeStage(ChainProcessor.java:98) at bt.processor.ChainProcessor.lambda$process$0(ChainProcessor.java:81) at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run$$$capture(CompletableFuture.java:1736) at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:834)
抛出异常的源码:
private <E extends BaseEvent> void addListener(Class<E> eventType, TorrentId torrentId, Consumer<E> listener) { Collection<Consumer<? extends BaseEvent>> listeners; if (torrentId == null) { listeners = this.listeners.computeIfAbsent(eventType, key -> ConcurrentHashMap.newKeySet()); } else { listeners = this.listenersOnTorrent.computeIfAbsent(torrentId, key -> new ConcurrentHashMap<>()) .computeIfAbsent(eventType, key -> ConcurrentHashMap.newKeySet()); } eventLock.writeLock().lock(); try { Consumer<E> safeListener = event -> { try { listener.accept(event); } catch (Exception ex) { LOGGER.error("Listener invocation failed", ex); } }; listeners.add(safeListener); } finally { eventLock.writeLock().unlock(); } }
Debug断点截图:
猜想
以上堆栈可以看到[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
这句描述,说明当前的使用的ExecutorService
并未创建线程或者创建的线程数量为0。这里我们可以大胆猜测,由于可用线程数量都是0,AtomicReference
调用wait()
方法后引起了InterruptedException
。
这里有进一步产生一个问题,我们并没有在实际使用中特意指定ExecutorService
,而是用的组件默认的ExecutorService
。那为什么为有Thread数量为0的情况呢?
通过分析源码发现,通过系统使用Google的guice这个IOC框架实现DI,其中@Inject
注解注入了TorrentProcessorFactory
,使用@ClientExecutor
指定了具体的ExecutorService
。
package java.util.concurrent.atomic;import java.util.function.UnaryOperator;import java.util.function.BinaryOperator;import sun.misc.Unsafe;public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; // 空歌白石:基于Unsafe实现原子操作 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 空歌白石:基于volatile实现原子操作 private volatile V value; public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } // 空歌白石:基于volatile实现原子操作 public final V get() { return value; } public final void set(V newValue) { value = newValue; } // 空歌白石:基于Unsafe实现原子操作 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } // 空歌白石:基于Unsafe实现原子操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; } public String toString() { return String.valueOf(get()); }}0
使用guice
的Binder
绑定ClientExecutor
,代码如下。
package java.util.concurrent.atomic;import java.util.function.UnaryOperator;import java.util.function.BinaryOperator;import sun.misc.Unsafe;public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; // 空歌白石:基于Unsafe实现原子操作 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 空歌白石:基于volatile实现原子操作 private volatile V value; public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } // 空歌白石:基于volatile实现原子操作 public final V get() { return value; } public final void set(V newValue) { value = newValue; } // 空歌白石:基于Unsafe实现原子操作 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } // 空歌白石:基于Unsafe实现原子操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; } public String toString() { return String.valueOf(get()); }}1
从上述代码可以看出在ExecutorServiceProvider
完成ExecutorService
的创建。
package java.util.concurrent.atomic;import java.util.function.UnaryOperator;import java.util.function.BinaryOperator;import sun.misc.Unsafe;public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; // 空歌白石:基于Unsafe实现原子操作 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 空歌白石:基于volatile实现原子操作 private volatile V value; public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } // 空歌白石:基于volatile实现原子操作 public final V get() { return value; } public final void set(V newValue) { value = newValue; } // 空歌白石:基于Unsafe实现原子操作 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } // 空歌白石:基于Unsafe实现原子操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; } public String toString() { return String.valueOf(get()); }}2
由于newCachedThreadPool
实现的ThreadPool的keepAliveTime
为60s
,猜测可能是这个时间过短引起。于是重新实现了newCachedThreadPool
但是问题仍然没有解决。
package java.util.concurrent.atomic;import java.util.function.UnaryOperator;import java.util.function.BinaryOperator;import sun.misc.Unsafe;public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; // 空歌白石:基于Unsafe实现原子操作 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 空歌白石:基于volatile实现原子操作 private volatile V value; public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } // 空歌白石:基于volatile实现原子操作 public final V get() { return value; } public final void set(V newValue) { value = newValue; } // 空歌白石:基于Unsafe实现原子操作 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } // 空歌白石:基于Unsafe实现原子操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; } public String toString() { return String.valueOf(get()); }}3
继续分析
上述思路受阻后,重新回到最原始的问题点,即java.lang.InterruptedException
的异常,再次查看wait()
方法的JDK注释,如下:
package java.util.concurrent.atomic;import java.util.function.UnaryOperator;import java.util.function.BinaryOperator;import sun.misc.Unsafe;public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; // 空歌白石:基于Unsafe实现原子操作 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 空歌白石:基于volatile实现原子操作 private volatile V value; public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } // 空歌白石:基于volatile实现原子操作 public final V get() { return value; } public final void set(V newValue) { value = newValue; } // 空歌白石:基于Unsafe实现原子操作 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } // 空歌白石:基于Unsafe实现原子操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; } public String toString() { return String.valueOf(get()); }}4
如果torrent.wait();
不抛出InterruptedException
,那么根据wait()
方法的定义可以看出,可以使用notify()
或notifyAll()
停止wait。因此,决定重新分析MetadataConsumer
类,将相对完整的源码贴出来,同时去掉非相关的部分代码。
package java.util.concurrent.atomic;import java.util.function.UnaryOperator;import java.util.function.BinaryOperator;import sun.misc.Unsafe;public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; // 空歌白石:基于Unsafe实现原子操作 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 空歌白石:基于volatile实现原子操作 private volatile V value; public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } // 空歌白石:基于volatile实现原子操作 public final V get() { return value; } public final void set(V newValue) { value = newValue; } // 空歌白石:基于Unsafe实现原子操作 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } // 空歌白石:基于Unsafe实现原子操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; } public String toString() { return String.valueOf(get()); }}5
通过重新分析MetadataConsumer
类,我们发现AtomicReference<Torrent>
的整个代码中只有一个地方使用了notifyAll
通知其他线程状态,截取代码如下:
package java.util.concurrent.atomic;import java.util.function.UnaryOperator;import java.util.function.BinaryOperator;import sun.misc.Unsafe;public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; // 空歌白石:基于Unsafe实现原子操作 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 空歌白石:基于volatile实现原子操作 private volatile V value; public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } // 空歌白石:基于volatile实现原子操作 public final V get() { return value; } public final void set(V newValue) { value = newValue; } // 空歌白石:基于Unsafe实现原子操作 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } // 空歌白石:基于Unsafe实现原子操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; } public String toString() { return String.valueOf(get()); }}6
通过关注这部分的log日志,发现了如下NPE问题。
package java.util.concurrent.atomic;import java.util.function.UnaryOperator;import java.util.function.BinaryOperator;import sun.misc.Unsafe;public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; // 空歌白石:基于Unsafe实现原子操作 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 空歌白石:基于volatile实现原子操作 private volatile V value; public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } // 空歌白石:基于volatile实现原子操作 public final V get() { return value; } public final void set(V newValue) { value = newValue; } // 空歌白石:基于Unsafe实现原子操作 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } // 空歌白石:基于Unsafe实现原子操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } // 空歌白石:基于Unsafe实现原子操作 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; } public String toString() { return String.valueOf(get()); }}7
进一步分析是在fromByteArray
类中对于边界值的处理有个bug。经过修复后,不再出现InterruptedException
了,问题得到解决。
上文中我们会提到过一个问题那么何时会抛出异常呢?,当时的回答是:如果任何线程在当前线程等待通知之前或期间中断了当前线程,便抛出此异常时清除当前线程的中断状态。正是由于NullPointerException
中断了正在运行的Thread,导致AtomicReference在调用wait方法时抛出了InterruptedException
异常。
总结
本文从最初的InterruptedException
开始详细分析了AtomicReference
相关的方方面面,最终也将实际工作中遇到的问题加以解决。
参考文献
https://cr.openjdk.java.net/~iris/se/11/latestSpec/api/java.base/java/util/Objects.html
https://www.cnblogs.com/dolphin0520/p/3920373.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
https://www.programcreek.com/java-api-examples/?class=java.util.concurrent.atomic.AtomicReference&method=wait
https://blog.csdn.net/wyaoyao93/article/details/115288129
https://blog.csdn.net/wyaoyao93/article/details/115367063
https://stackoverflow.com/questions/20087173/how-to-do-a-lazy-create-and-set-with-atomicreference-in-a-safe-and-efficient-man
https://www.apiref.com/java11-zh/java.base/java/util/concurrent/atomic/AtomicLongFieldUpdater.html
原文:https://juejin.cn/post/7122304178919047175