从InterruptedException深入理解AtomicReference的方方面面

多线程系列(十六) -常用并发原子类详解 在 Java 的java.util.concurrent包中,除了提供底层锁、并发同步等工具类以外,还提供了一组原子操作类,大多以Atomic开头,他们位于java.util.concurrent.atomic包下。 阅读详情

前言

在近期的测试中遇到了一个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:docs.oracle.com/javase/8/do…

AtomicReference的作用是对对象进行原子操作。它提供了一种读和写都是原子性的对象引用变量。

同时,原子性意味着多个线程试图改变同一个 AtomicReference(例如比较和交换操作)将不会使得 AtomicReference 处于不一致的状态。

atomic 包

提到AtomicReference就一定会提到原子包:java.util.concurrent.atomic,在这里 JDK 提供了众多的针对原子操作的类。包括:

  • Number

    • AtomicInteger
      • 提供对int类型的原子操作,是对基本类型封装的一个代表;
    • AtomicBoolean
      • 提供对boolean类型的原子操作
    • AtomicLong
      • 提供对long类型的原子操作
    • DoubleAdder
      • 一个或多个变量一起保持最初为零的总和为double 。
      • 当跨线程争用更新(方法add(double) )时,变量集可以动态增长以减少争用。
      • 方法sum() (或等效地, doubleValue() )返回保持总和的变量的当前总和。
      • 线程内或线程之间的累积顺序无法保证。
      • 因此,如果需要数值稳定性,则该类可能不适用,尤其是在组合基本上不同数量级的值时。
    • LongAdder
      • 一个或多个变量,它们共同维持最初的零long总和。
      • 当跨线程争用更新(方法add(long) )时,变量集可以动态增长以减少争用。
      • 方法sum() (或等效地, longValue() )返回保持总和的变量的当前总和。
      • 当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共和时,此类通常优于AtomicLong 。
      • 在低更新争用下,这两个类具有相似的特征。 但在高争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
    • Striped64
    • DoubleAccumulator
      • 使用提供的函数一起维护运行的double值的一个或多个变量。
      • 当更新(方法accumulate(double) )跨线程争用时,变量集可以动态增长以减少争用。
      • 方法get() (或等效地, doubleValue() )返回维护更新的变量的当前值。
      • 当多个线程更新公共值时,此类通常优于备选方案,该公共值用于诸如经常更新但读取频率较低的摘要统计信息之类的目的。
      • 提供的累加器功能应该是无副作用的,因为当尝试的更新由于线程之间的争用而失败时可以重新应用它。
      • 对于可预测的结果,累加器函数应该在使用上下文中所需的浮点容差内是可交换的和关联的。
      • 该函数应用现有值(或标识)作为一个参数,给定更新作为另一个参数。 例如,要保持运行的最大值,您可以提供Double::max以及Double.NEGATIVE_INFINITY作为标识。
      • 线程内或线程之间的累积顺序无法保证。 因此,如果需要数值稳定性,则该类可能不适用,尤其是在组合基本上不同数量级的值时。
    • LongAccumulator
      • 使用提供的函数一起维护运行的long值的一个或多个变量。 当跨线程争用更新(方法accumulate(long) )时,变量集可以动态增长以减少争用。 方法get() (或等效地, longValue() )返回维护更新的变量的当前值。
      • 当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共值时,此类通常优于AtomicLong 。 在低更新争用下,这两个类具有相似的特征。 但在高争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
      • 线程内或线程之间的累积顺序无法保证且不能依赖,因此该类仅适用于累积顺序无关紧要的函数。 提供的累加器功能应该是无副作用的,因为当尝试的更新由于线程之间的争用而失败时可以重新应用它。 对于可预测的结果,累加器函数应该是关联的和可交换的。 该函数应用现有值(或标识)作为一个参数,给定更新作为另一个参数。 例如,要保持运行的最大值,您可以提供Long::max以及Long.MIN_VALUE作为标识。
      • 类LongAdder提供了此类功能的类比,用于维护计数和总和的常见特殊情况。 电话new LongAdder()相当于new LongAccumulator((x, y) -> x + y, 0L) 。
    • AtomicIntegerFieldUpdater
      • 原子地更新一个对象的int类型的field,这个int类型的field必须是被volatile修饰的;
      • 基于反射的实用程序,可以对指定类的指定volatile int字段进行原子更新。 此类设计用于原子数据结构,其中同一节点的多个字段独立地受原子更新的影响。
      • 请注意, compareAndSet方法的保证比其他原子类弱。 因为此类无法确保该字段的所有使用都适用于原子访问的目的,所以它只能保证在同一更新程序上对compareAndSet和set其他调用的原子性。
    • AtomicLongFieldUpdater
      • 原子地更新一个对象的long类型的field,这个long类型的field必须是被volatile修饰的;
      • 基于反射的实用程序,可以对指定类的指定volatile long字段进行原子更新。 此类设计用于原子数据结构,其中同一节点的多个字段独立地受原子更新的影响。
      • 请注意, compareAndSet方法的保证比其他原子类弱。 由于此类无法确保该字段的所有使用都适用于原子访问,因此只能在同一更新程序上对compareAndSet和set其他调用保证原子性。
  • Array

    • AtomicIntegerArray
      • 提供对int数组的原子操作,可以原子地更新数组里的某个index上的值;
    • AtomicLongArray
      • 提供对long数组的原子操作,可以原子地更新数组里的某个index上的值;
  • Reference

    • AtomicReference
      • 原子更新引用类型,提供对引用类型的原子操作
      • 但是并不是说可以原子地操作引用的对象里的字段,可以将引用原子地指向两一个对象;
    • AtomicReferenceArray
      • 原子地更新一个reference类型的数组。
    • AtomicReferenceFieldUpdater
      • 原子更新引用类型里的字段
    • AtomicMarkableReference
      • 原子更新带有标记位的引用类型。
      • 需要传入一个reference和一个boolean类型的标志位,可以原子地更新reference和标志位;
    • AtomicStampedReference
      • 与AtomicMarkableReference类似,不过把标志位换成了一个int值,原子的更新reference和int值;

继承自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 修饰之后,那么就具备了两层语义:

  1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2. 禁止进行指令重排序。

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有三个操作数:

  1. 内存值V
  2. 旧的预期值A
  3. 要修改的值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();
    }

    // 空歌白石:略去无关代码
}

@Produces
public 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异常方法:

  1. java.lang.Object.wait()及其重载方法
    • 会进入等待区等待。
  2. java.lang.Thread.sleep()及其重载方法
    • 会睡眠执行参数内所设置的时间。
  3. 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。


private ExecutorService executor;

@Inject
public TorrentProcessorFactory( // 空歌白石:略去无关的参数
                                @ClientExecutor ExecutorService executor,
                                // 空歌白石:略去无关的参数
    ) {
    // 空歌白石:略去无关代码

    this.executor = executor;

    // 空歌白石:略去无关代码
}

使用guice的Binder绑定ClientExecutor,代码如下。

binder.bind(ExecutorService.class).annotatedWith(ClientExecutor.class)
        .toProvider(ExecutorServiceProvider.class).in(Singleton.class);

从上述代码可以看出在ExecutorServiceProvider完成ExecutorService的创建。

package bt.service;

import bt.runtime.Config;
import com.google.inject.Inject;
import com.google.inject.Provider;

import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @since 1.0
 */
public class ExecutorServiceProvider implements Provider<ExecutorService> {

    private volatile ExecutorService executorService;
    private final Object lock;
    private final Config config;

    @Inject
    public ExecutorServiceProvider(Config config) {
        this.lock = new Object();
        this.config = config;
    }

    @Override
    public ExecutorService get() {

        if (executorService == null) {
            synchronized (lock) {
                if (executorService == null) {
                    executorService = Executors.newCachedThreadPool(new ThreadFactory() {

                        private AtomicInteger threadId = new AtomicInteger(1);

                        @Override
                        public Thread newThread(Runnable r) {
                            return new Thread(r, Objects.requireNonNull(getNamePrefix()) + "-" + threadId.getAndIncrement());
                        }
                    });
                }
            }
        }

        return executorService;
    }

    /**
     * @since 1.6
     */
    protected String getNamePrefix() {
        return String.format("%d.bt.service.executor-thread", config.getAcceptorPort());
    }
}

由于newCachedThreadPool实现的ThreadPool的keepAliveTime为60s,猜测可能是这个时间过短引起。于是重新实现了newCachedThreadPool但是问题仍然没有解决。

/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available, and uses the provided
* ThreadFactory to create new threads when needed.
* @param threadFactory the factory to use when creating new threads
* @return the newly created thread pool
* @throws NullPointerException if threadFactory is null
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>(),
                                    threadFactory);
}

继续分析

上述思路受阻后,重新回到最原始的问题点,即java.lang.InterruptedException的异常,再次查看wait()方法的JDK注释,如下:

Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).
The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
           synchronized (obj) {
               while (<condition does not hold>)
                   obj.wait();
               ... // Perform action appropriate to condition
           }

This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.
Throws:
IllegalMonitorStateException – if the current thread is not the owner of the object's monitor.
InterruptedException – if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
See Also:
notify(), notifyAll()

如果torrent.wait();不抛出InterruptedException,那么根据wait()方法的定义可以看出,可以使用notify()或notifyAll()停止wait。因此,决定重新分析MetadataConsumer类,将相对完整的源码贴出来,同时去掉非相关的部分代码。

public class MetadataConsumer {

    // 空歌白石:略去无关代码

    private final TorrentId torrentId;

    // set immediately after metadata has been fetched and verified
    private final AtomicReference<Torrent> torrent;

    // 空歌白石:略去无关代码

    public MetadataConsumer(IMetadataService metadataService,
                            TorrentId torrentId,
                            Config config,
                            EventSource eventSource) {

        // 空歌白石:略去无关代码

        this.torrent = new AtomicReference<>();

        // 空歌白石:略去无关代码
    }

    @Consumes
    public void consume(UtMetadata message, MessageContext context) {
        Peer peer = context.getPeer();
        // being lenient herer and not checking if the peer advertised ut_metadata support
        StateContext stateContext = states.get(peer);
        switch (message.getType()) {
            case DATA: {
                int totalSize = message.getTotalSize().get();
                if (totalSize >= metadataExchangeMaxSize) {
                    throw new IllegalStateException("Declared metadata size is too large: " + totalSize +
                            "; max allowed is " + metadataExchangeMaxSize);
                }
                if (stateContext != null) {
                    stateContext.requestedIndexes.remove(message.getPieceIndex());
                    if (stateContext.state != State.HAS_METADATA) {
                        stateContext.state = State.HAS_METADATA;
                    }
                }
                processMetadataBlock(message.getPieceIndex(), totalSize, message.getData().get());
            }
            break;
            case REJECT: {
                stateContext.rejectedTime = System.currentTimeMillis();
                stateContext.state = State.REJECTED;
            }
            break;
            default: {
                // ignore
            }
        }
    }

    private void processMetadataBlock(int pieceIndex, int totalSize, byte[] data) {
        if (metadata == null) {
            synchronized (this) {
                if (metadata == null) {
                    metadata = new ExchangedMetadata(totalSize, metadataExchangeBlockSize);
                    blocksNeedRequest = createBlockIndexQueue(metadata.getBlockCount());
                }
            }
        }

        if (!metadata.isBlockPresent(pieceIndex)) {
            metadata.setBlock(pieceIndex, data);

            if (metadata.isComplete()) {
                byte[] digest = metadata.getSha1Digest();
                if (Arrays.equals(digest, torrentId.getBytes())) {
                    Torrent fetchedTorrent = null;
                    try {
                        fetchedTorrent = metadataService.fromByteArray(metadata.getBytes());
                    } catch (Exception e) {
                        LOGGER.error("Processing of metadata failed: " + torrentId, e);
                        metadata = null;
                    }

                    if (fetchedTorrent != null) {
                        synchronized (torrent) {
                            torrent.set(fetchedTorrent);
                            states.clear();
                            torrent.notifyAll();
                        }
                    }
                } else {
                    // 空歌白石:略去无关代码
                }
            }
        }
    }

    @Produces
    public void produce(Consumer<Message> messageConsumer, MessageContext context) {
        // stop here if metadata has already been fetched
        if (torrent.get() != null) {
            return;
        }

        // 空歌白石:略去无关代码
    }

    /**
     * @return Torrent, blocking the calling thread if it hasn't been fetched yet
     */
    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();
    }

    // 空歌白石:略去无关代码
}

通过重新分析MetadataConsumer类,我们发现AtomicReference<Torrent>的整个代码中只有一个地方使用了notifyAll通知其他线程状态,截取代码如下:

Torrent fetchedTorrent = null;
try {
    fetchedTorrent = metadataService.fromByteArray(metadata.getBytes());
} catch (Exception e) {
    LOGGER.error("Processing of metadata failed: " + torrentId, e);
    metadata = null;
}

if (fetchedTorrent != null) {
    synchronized (torrent) {
        torrent.set(fetchedTorrent);
        states.clear();
        torrent.notifyAll();
    }
}

通过关注这部分的log日志,发现了如下NPE问题。

java.lang.NullPointerException
    at bt.torrent.MetadataService.fromByteArray(MetadataService.java:31)
    at bt.torrent.messaging.MetadataConsumer.processMetadataBlock(MetadataConsumer.java:157)
    at bt.torrent.messaging.MetadataConsumer.consume(MetadataConsumer.java:125)
    at bt.torrent.messaging.DefaultMessageRouter$CollectingCompilerVisitor$1.consume(DefaultMessageRouter.java:167)
    at bt.torrent.messaging.DefaultMessageRouter.lambda$doConsume$2(DefaultMessageRouter.java:129)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at bt.torrent.messaging.DefaultMessageRouter.doConsume(DefaultMessageRouter.java:126)
    at bt.torrent.messaging.DefaultMessageRouter.consume(DefaultMessageRouter.java:118)
    at bt.torrent.messaging.RoutingPeerWorker.accept(RoutingPeerWorker.java:67)
    at bt.torrent.messaging.RoutingPeerWorker.accept(RoutingPeerWorker.java:41)
    at bt.torrent.messaging.TorrentWorker$PieceAnnouncingPeerWorker.accept(TorrentWorker.java:380)
    at bt.torrent.messaging.TorrentWorker.lambda$consume$8(TorrentWorker.java:183)
    at java.base/java.util.Optional.ifPresent(Optional.java:183)
    at bt.torrent.messaging.TorrentWorker.consume(TorrentWorker.java:183)
    at bt.torrent.messaging.TorrentWorker.lambda$addPeer$6(TorrentWorker.java:177)
    at bt.MultiThreadMessageDispatcher$MessageDispatchingLoop.processConsumer(MultiThreadMessageDispatcher.java:148)
    at bt.MultiThreadMessageDispatcher$MessageDispatchingLoop.run(MultiThreadMessageDispatcher.java:108)
    at bt.MultiThreadMessageDispatcher.lambda$createAndSubmitTask$3(MultiThreadMessageDispatcher.java:292)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.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)

进一步分析是在fromByteArray类中对于边界值的处理有个bug。经过修复后,不再出现InterruptedException了,问题得到解决。

上文中我们会提到过一个问题那么何时会抛出异常呢?,当时的回答是:如果任何线程在当前线程等待通知之前或期间中断了当前线程,便抛出此异常时清除当前线程的中断状态。正是由于NullPointerException中断了正在运行的Thread,导致AtomicReference在调用wait方法时抛出了InterruptedException异常。

总结

本文从最初的InterruptedException开始详细分析了AtomicReference相关的方方面面,最终也将实际工作中遇到的问题加以解决。

原子类 AtomicReference 详解 通过对 AtomicInteger、AtomicBoolean 和 AtomicLong 分析我们发现,这三个原子类只能对单个变量进行原子操作,那么我们如果要对多个变量进行原子操作,这三个类就无法实现了。那如果要进行多个变量进行原子操作呢?操作方式就是,先把 多个变量封装成一个类,然后通过 AtomicReference 进行操作。 阅读详情

相关推荐

Java volatile关键字深度解析:可见性与有序性的底层原理与实战

在并发编程中,内存可见性和指令重排序是多线程环境下的核心挑战。Java内存模型(JMM)通过定义happens-before规则,为开发者提供了一套内存一致性保证机制。volatile关键字正是JMM的一种轻量级同步工具,它通过插入内存屏障指令,确保对变量的写操作能立即刷新到主内存,并使其他线程的缓存失效,从而解决可见性问题;同时禁止编译器和处理器对相关指令进行重排序,保障有序性。这一机制在状态标志、一次性安全发布等一写多读场景中具有重要技术价值,能有效避免因缓存不一致或指令乱序导致的程序逻辑错误。然而,v

weixin_30920597的博客 426

AtomicReference

本文翻译自http://tutorials.jenkov.com/java-util-concurrent/atomicreference.html,人工翻译,仅供学习交流。 AtomicReference AtomicReference类提供了一个对象引用变量,它可以原子地读写。原子是指多个线程试图更改同一个AtomicReference(例如,CAS)不会使AtomicReference最终处于不一致的状态。AtomicReference甚至有一个高级的compareAndSet()方法让您将引用与期望

lzc_23的博客 1843

Java并行编程演进:从线程池到虚拟线程的实战选型指南

并发编程是现代软件工程的核心概念,旨在通过同时执行多个任务来提升系统吞吐量和响应速度。其基本原理是利用多线程技术,在共享内存或消息传递的机制下协调任务执行。在Java生态中,这一技术价值尤为突出,能够有效应对高并发、大数据处理等场景下的性能挑战。应用场景广泛覆盖Web服务器、微服务、数据处理管道等IO密集型系统。本文聚焦Java并行执行的演进路径,深入解析传统线程池的管理策略、CompletableFuture的异步编排能力,以及革命性的虚拟线程技术。针对线程安全、资源竞争等常见痛点,探讨了synchron

weixin_34293246的博客 427

原子引用(AtomicReference)

1.AtomicReference:原子引用,可以用于乐观锁,避免线程上下文的切换,提升系统的运行效率。相关的“ABA”问题的代码如下: static AtomicReference<String>ref=new AtomicReference<>("A"); public static void main(String[] args) throws InterruptedException { System.out.println("start..")

weixin_44836361的博客 1233

Vue+Spring Boot构建流体力学虚拟仿真平台:架构设计与工程实践

虚拟仿真技术通过计算机模拟真实世界过程,其核心原理在于利用数学模型和算法对物理现象进行数字化重现。在工程教育领域,这项技术能突破时间、空间和成本限制,将抽象理论转化为直观、可交互的体验,其技术价值在于提升教学效率与安全性。典型的应用场景包括需要昂贵设备或存在操作风险的实验教学,例如流体力学中的流动可视化分析。本文聚焦于如何运用Vue.js前端框架与Spring Boot后端框架,构建一个支持实时数据交互与动态可视化的教学仿真平台。该平台通过WebSocket实现前后端实时通信,并利用ECharts等库进行流

weixin_33428613的博客 332

ISP Tuning实战指南:从基础到高级的色彩与亮度优化

本文详细介绍了ISP Tuning的基础概念与高级调优技术,包括黑电平校正(BLC)、自动白平衡(AWB)、色彩校正矩阵(CCM)和Gamma曲线调整等核心算法。通过实战案例和调试技巧,帮助工程师提升摄像头成像质量,解决高光过曝、暗部噪点等常见问题,适用于智能门铃、车载摄像头等多种场景。

weixin_30879169的博客 108

高并发编程之AtomicReference原子类讲解

高并发编程之Atomic原子类讲解

weixin_45080272的博客 4709

脑电分析:为什么大脑信号会“变同步”?——事件相关同步化与去同步化 (ERS/ERD) 全景解析

脑电分析中的事件相关同步化(ERS)和去同步化(ERD)是研究大脑动态节律的重要方法,反映特定任务下脑电频段能量的变化。ERD表示神经元活动分散(功率下降),提示脑区活跃处理信息;ERS则反映神经元同步(功率增强),常与静息状态相关。本文详解了ERS/ERD的生理机制、计算流程(包括基线校正和功率谱分析),并提供了Python实现示例,展示如何通过Welch法和STFT进行时频分析,生成ERD/ERS热力图。该技术广泛应用于运动想象脑机接口、癫痫检测等领域,为理解大脑功能状态提供了量化工具。

qq_32516809的博客 1396

Java并发编程:深入理解CAS机制原理与应用场景

在并发编程中,线程安全是核心挑战之一。传统锁机制通过互斥访问实现同步,但会带来性能开销和活跃性问题。CAS(Compare-And-Swap)作为一种硬件级原子操作,提供了一种无锁的乐观并发控制方案。其原理基于CPU指令(如x86的CMPXCHG),通过比较内存值与预期值来决定是否更新,整个过程具有原子性。这种机制的技术价值在于避免了线程阻塞和上下文切换,显著提升了高并发场景下的性能。在Java中,通过Unsafe类(或VarHandle)暴露CAS操作,结合volatile保证内存可见性,广泛应用于JUC

weixin_34348174的博客 814

【毕设】基于LoRa的物联网控制器:从原理图到PCB的完整硬件设计指南

本文提供了一份完整的基于LoRa的物联网控制器硬件设计指南,涵盖从系统框图、原理图设计到PCB布局布线的全流程。详细讲解了电源电路、单片机最小系统、信号接口以及LoRa无线模块等核心模块的设计要点与抗干扰技巧,并分享了实用的调试心得,旨在帮助电子、物联网专业学生顺利完成毕业设计,掌握低功耗远距离物联网节点的硬件开发实践。

weixin_42126677的博客 306

SAP MRKO与MIRO的对比分析:为何寄售结算需要定制开发?

本文深入分析了SAP系统中MRKO与MIRO在寄售结算中的功能差异,揭示了标准MRKO在财务控制和业务流程上的局限性。通过定制开发实践,如BAdI增强和界面优化,企业可显著提升结算效率与准确性。文章还探讨了未来SAP S/4HANA在寄售结算中的智能化趋势,为相关企业提供技术参考。

weixin_29300241的博客 420

深入解析CAS与自旋锁:高并发场景下的无锁编程利器

在并发编程中,保证数据一致性的核心挑战在于如何安全地操作共享变量。传统互斥锁通过阻塞线程实现同步,但会引入上下文切换开销。相比之下,CAS(Compare-And-Swap)作为一种乐观锁机制,基于硬件原子指令实现,允许线程直接尝试更新变量,仅在冲突时重试,避免了线程挂起。这种通过循环重试实现互斥的机制即为自旋锁,它在锁持有时间极短的场景下性能优势显著。CAS与自旋锁是构建高性能无锁数据结构的基础,广泛应用于计数器、状态标志更新等**高并发**场景。本文通过一个**竞态条件**引发的线上问题,深入剖析CAS

weixin_34072637的博客 286

YOLOv11【第三章:高阶应用与落地篇·第7节】多任务联合训练(Multi-Task Learning):检测+分割+关键点三头并进!

🏆 本文收录于专栏 《YOLOv11实战:从入门到深度优化》。本专栏围绕 YOLOv11 的改进、训练、部署与工程优化 展开,系统梳理并复现当前主流的 YOLOv11 实战案例与优化方案,内容目前已覆盖 分类、检测、分割、追踪、关键点、OBB 检测 等多个方向。

**My Coding Family** 728

Java多线程暴力破解加密ZIP文件:原理、实现与性能优化实战

在计算机安全与数据恢复领域,暴力破解是一种通过系统化尝试所有可能组合来获取访问权限的基础方法。其核心原理在于利用计算资源对有限密钥空间进行穷举搜索,技术价值体现在对加密强度的实际测试与遗忘凭证的恢复。在应用场景上,常与字典攻击结合,用于评估密码策略或恢复合法数据。本文聚焦于传统ZipCrypto加密的ZIP文件,通过Java多线程技术将破解任务并行化,显著提升尝试速度。项目深入整合了线程池管理与任务调度策略,并基于Apache Commons Compress库实现高效验证,为处理计算密集型任务提供了典型的

weixin_34392435的博客 363

nvidia系列教程-AGX-Orin linux源码下载编译

NVIDIA AGX Orin 是一款高性能的嵌入式计算平台,广泛应用于人工智能、自动驾驶、机器人等领域。为 AGX Orin 编译定制的 Linux 内核,可以更好地适应特定应用需求,增强系统的灵活性。本文将详细介绍如何下载、配置和编译适用于 AGX Orin 的 Linux 内核源码。本文介绍了如何为 NVIDIA AGX Orin 平台下载、配置和编译定制的 Linux 内核源码。通过编译和部署定制内核,您可以更好地控制系统的行为,并根据特定应用需求进行优化。

flypig has a dream! 1283

ARM功耗管理之功耗状态及功耗模式

思考:功耗状态?功耗模式?Core&Cluster&Device&SoC如何映射?

AI-安全-功耗 783

Java并发编程:同步机制原理与实战应用

在多线程编程中,同步机制是确保线程安全的核心技术。Java内存模型(JMM)定义了线程与内存的交互规则,解释了原子性、可见性和有序性三大并发问题。通过synchronized关键字、volatile变量和Lock接口等同步工具,开发者可以控制线程对共享资源的访问。这些机制在电商库存管理、高并发计数器等场景中发挥关键作用。文章深入分析Java内置同步机制的工作原理,比较synchronized与ReentrantLock的差异,并介绍ConcurrentHashMap等线程安全集合类的实现。掌握这些技术能有效

weixin_34129696的博客 423

Error: Flash Download failed - Target DLL has been cancelled

由于换了新电脑,keil重装了下,然而之前的MCU的支持包没有安装,以及一些其他的问题,导致可以编译但是不能将程序烧录到单片机中。现整理一下解决思路: 1、将支持包下载并添加: 找到你需要用的MCU的具体型号: 例如: 点击网址,下载安装包,或者在csdn等其他网址下载安装包,将下载好的pack包放到keil5的安装目录下的.Download中: 点击file中的import,将路径选择到.........

hanhan的博客 5万+

Java线程基础与并发编程实践指南

线程作为操作系统调度的基本单位,是Java并发编程的核心概念。从JVM视角看,每个线程对应一个Thread实例,共享进程资源但独立执行。理解线程生命周期(NEW、RUNNABLE、BLOCKED等状态)和同步机制(synchronized、volatile、Lock)是开发线程安全应用的基础。在Java生态中,线程池(ThreadPoolExecutor)通过核心参数配置实现任务高效调度,而AQS框架则为锁实现提供底层支持。实际开发中,应优先使用ConcurrentHashMap等并发容器,遵循减少锁粒度、

weixin_34208283的博客 365

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Java多线程面试核心:从JMM、锁机制到线程池调优与并发容器实战

并发编程是构建高性能、高可靠分布式系统的基石,其核心在于解决多线程环境下的数据一致性与资源协调问题。Java内存模型(JMM)定义了线程间共享变量的访问规则,是理解可见性、有序性等线程安全问题的理论基础。基于此,Java提供了synchronized内置锁、ReentrantLock显式锁以及volatile、原子变量等多种同步机制,以满足不同场景下的互斥与通信需求。在实际工程中,线程池(ThreadPoolExecutor)是管理线程生命周期、控制并发度的关键组件,其核心参数如核心线程数、工作队列和拒绝策

weixin_34161029的博客 535

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Java并发编程核心:JMM、锁与原子操作实战

并发编程是现代Java开发的核心技能,其本质是通过多线程实现任务并行处理。Java内存模型(JMM)定义了线程与内存交互的规范,解决可见性、原子性和有序性三大核心问题。在实际工程中,synchronized锁优化、CAS原子操作以及ConcurrentHashMap等并发容器是高频使用的关键技术。特别是在高并发场景如电商秒杀系统中,合理使用ReentrantLock和LongAdder能显著提升性能。理解happens-before原则和锁升级机制,可以帮助开发者编写出更高效的线程安全代码。

weixin_30274627的博客 399

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

为什么让 AI 做逆向工程总像在猜?REA 完整拆解

你问 AI“这个 App 到底怎么实现的”,答案是不是总有模有样,追问证据却含糊其辞?多半在猜。AI 逆向工程和胡说八道之间,差的只是一件事:模型手里有没有真实观测。 ## 🖥️ 让 AI 逆向工程在本地跑起来,还自带证据 [![REA 在 Hopper 中执行本地二进制逆向分析的界面](https://raw.gitcode.com/GitHub_Trending/rea2/rea/raw

gitblog_01173的博客 427

Java高并发与多线程面试核心:从JMM原理到秒杀系统设计实战

并发编程是构建高性能、高可用软件系统的核心技术基础,其核心在于解决多线程环境下对共享资源的安全访问与协调问题。从原理层面,Java内存模型(JMM)通过定义主内存与工作内存的交互规则,以及happens-before原则,为并发操作提供了可见性与有序性的理论保障。在工程实践中,开发者需要掌握线程安全、锁机制(如synchronized与ReentrantLock)、原子变量(CAS)以及并发容器等核心工具,以应对数据竞争、死锁等经典并发问题。这些技术的价值在于能够有效提升系统吞吐量、降低响应延迟,是构建电商

weixin_34239169的博客 334

高性能乐观并发缓存:无锁设计解决高并发读写冲突

在并发编程与系统架构中,数据一致性(Data Consistency)与系统性能(Performance)的平衡是核心挑战。传统悲观锁(Pessimistic Locking)通过串行化访问保证强一致性,但常引发锁竞争,成为吞吐量瓶颈。乐观并发控制(Optimistic Concurrency Control, OCC)则提供了一种不同的思路:它假设冲突不常发生,允许事务先执行,仅在提交时检测版本冲突,从而在无冲突时获得接近无锁的高性能。这一思想在数据库领域成熟后,正被广泛应用于缓存层等对延迟敏感的场景。其

cuiji1279的博客 414

多线程教程(二十六)字段更新器、原子累加器

多线程教程(二十六)字段更新器、原子累加器 一、字段更新器 常见的字段更新器: AtomicReferenceFieldUpdater // 域 字段 AtomicIntegerFieldUpdater AtomicLongFieldUpdater 利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常 Exception in thread "main" java.lang.IllegalArgumentException

qq_30491663的博客 409

Java 比Automic更高效的累加器

与AtomicLong相比,DoubleAdder在高并发环境下能够提供更好的性能,因为它采用了分散(Striped)的设计,将内部的数据分为多个独立的单元进行累加,减少了竞争,从而提高了吞吐量。DoubleAccumulator也用于高效处理double类型的累加操作,不同的是它可以自定义累加的逻辑。compareAndSet 方法是 AtomicReference 类的原子操作方法,它会比较当前值是否与期望值相等,如果相等,则更新为新的值。如果当前值与期望值不相等,那么更新操作失败,线程需要重试。

FLGB 710
上一篇: 不谈源码,聊聊位运算的实际应用
下一篇: 聊聊Tomcat架构和生命周期
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值