面试官突击一问:你来讲讲AQS是什么吧?都是怎么用的?

Java秋招高效突击:JUC、JVM、MySQL与Spring核心考点深度解析 并发编程是构建高性能、高可用分布式系统的核心技术,其核心在于通过多线程、锁、原子操作等机制,有效利用多核CPU资源,提升程序执行效率。理解其底层原理,如Java内存模型(JVM)、锁优化、线程池工作机制,对于解决实际开发中的性能瓶颈、数据一致性等工程问题至关重要。在微服务、大数据处理等高并发应用场景中,扎实的并发与JVM知识是后端工程师的核心竞争力。本文聚焦Java面试高频考点,针对JUC并发工具包、JVM内存模型与垃圾回收、MySQL索引与事务、Spring框架原理等核心领域,提供一套高效的突击路径与实战 阅读详情

前言

在Java面试的时候,多线程相关的知识是躲不掉的,肯定会被问。我就被问到了AQS的知识,就直接了当的问,AQS知道是什么吧,来讲讲它是怎么实现的,以及哪些地方用到了它。当时自己确实没有讲好,所以这次来总结一下这个知识点。

image

 

此外,这边还整理了包括但不限于:分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货,如若需要学习借鉴,可“<click看看>”

什么是AQS

AQS全称是AbstractQueuedSynchronizer,形如其名,抽象队列同步器。 AQS定义了两种资源共享模式:

  • 独占式,每次只能有一个线程持有锁,例如ReentrantLock实现的就是独占式的锁资源。

  • 共享式,允许多个线程同时获取锁,并发访问共享资源,ReentrantWriteLock和CountDownLatch等就是实现的这种模式。

它维护了一个volatile的state变量和一个FIFO(先进先出)的队列。 其中state变量代表的是竞争资源标识,而队列代表的是竞争资源失败的线程排队时存放的容器。


public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
    ...
    /**
     * The synchronization state.
     */
    private volatile int state;
    /**
     * Wait queue node class.
     **/
     static final class Node {
        ...
    }
    ...
}

AQS中提供了操作state的方法:

  • getState();

  • setState();

  • compareSetState();


protected final int getState() {
    return state;
}
protected final void setState(int newState) {
    state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

因为AbstractQueuedSynchronizer是一个抽象类,他采用模板方法的设计模式,规定了独占和共享模式需要实现的方法,并且将一些通用的功能已经进行了实现,所以不同模式的使用方式,只需要自己定义好实现共享资源的获取与释放即可,至于具体线程在等待队列中的维护(获取资源入队列、唤醒出队列等),AQS已经实现好了。

所以根据共享资源的模式一般实现的方法有如下几个:

  • isHeldExclusively();// 是否为独占模式;但是只有使用到了Condition的,才需要去实现它。例如:ReentrantLock。

  • boolean tryAcquire(int arg); // 独占模式;尝试获取资源,成功返回true,失败返回false。

  • boolean tryRelease(int arg) ; // 独占模式;尝试释放资源,成功返回true,失败返回false。

  • int tryAcquireShared(int arg); // 共享模式;尝试获取资源,负数表示失败;0表示成功,但是没有剩余可用资源了;正数表示成功,且有剩余可用资源。

  • boolean tryReleaseShared(int arg) ; // 共享模式;尝试释放资源,若释放资源后允许唤醒后续等待节点返回true,否则返回false。

上面的这几个方法在AbstractQueuedSynchronizer这个抽象类中,都没有被定义为abstract的,说明这些方法都是可以按需实现的,共享模式下可以只实现共享模式的方法(例如CountDownLatch),独占模式下可以只实现独占模式的方法(例如ReentrantLock),也支持两种都实现,两种模式都使用(例如ReentrantReadWriteLock)。

AQS源码分析

我们先简单介绍AQS的两种模式的实现类的代表ReentrantLock(独占模式)和CountDownLatch(共享模式),是如何来共享资源的一个过程,然后再详细通过AQS的源码来分析整个实现过程。

  • ReentrantLock在初始化的时候state=0,表示资源未被锁定。当A线程执行lock()方法时,会调用tryAcquire()方法,将AQS中队列的模式设置为独占,并将独占线程设置为线程A,以及将state+1。 这样在线程A没有释放锁前,其他线程来竞争锁,调用tryAcquire()方法时都会失败,然后竞争锁失败的线程就会进入到队列中。当线程A调用执行unlock()方法将state=0后,其他线程才有机会获取锁(注意ReentrantLock是可重入的,同一线程多次获取锁时state值会进行垒加的,在释放锁时也要释放相应的次数才算完全释放了锁)。

  • CountDownLatch会将任务分成N个子线程去执行,state的初始值也是N(state与子线程数量一致)。N个子线程是并行执行的,每个子线程执行完成后countDown()一次,state会通过CAS方式减1。直到所有子线程执行完成后(state=0),会通过unpark()方法唤醒主线程,然后主线程就会从await()方法返回,继续后续操作。

独占模式分析

在AbstractQueuedSynchronizer的类里面有一个静态内部类Node。它代表的是队列中的每一个节点。 其中Node节点有如下几个属性:


// 节点的状态
volatile int waitStatus;
// 当前节点的前一个节点
volatile Node prev;
// 当前节点的后一个节点
volatile Node next;
// 当前节点中所包含的线程对象
volatile Thread thread;
// 等待队列中的下一个节点
Node nextWaiter;

每个属性代表的什么,已经写在代码注释中了。其中Node类中还有几个常量,代表了几个节点的状态(waitStatus)值。


/** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

首先节点的状态值waitStatus默认是0,然后下面几个常量有自己具体的含义。 CANCELLED = 1; 代表的是当前节点从同步队列中取消,当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。 SIGNAL = -1; 代表后继节点处于等待状态。后继结点入队时,会将前继结点的状态更新为SIGNAL。 CONDITION = -2; 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了 signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中。 PROPAGATE = -3; 表示在共享模式下,前继节点在释放资源后会唤醒后继节点,并将这种共享模式传播下去。

通过上面几个固定的常量值,我们可以看出节点状态中通常负数值通常表示节点处于有效的等待状态,而正数值代表节点已经被取消了。

所以AQS源码中有很多地方都用waitStatus>0或waitStatus<0这种方式来判断队列中节点的是否正常。

独占模式下,只能有一个线程占有锁资源,其他竞争资源的线程,在竞争失败后都会进入到等待队列中,等待占有锁资源的线程释放锁,然后再重新被唤醒竞争资源。

ReentrantLock加锁过程

ReentrantLock默认是非公平锁,就是说,线程在竞争锁的时候并不是按照先来后到的顺序来获取锁的,但是ReentrantLock也是支持公平锁的,在创建的时候传入一个参数值即可。 下面我们以ReentrantLock默认情况下的加锁来分析AQS的源码。 ReentrantLock并没有直接继承AQS类,而是通过内部类来继承AQS类的,这样自己的实现功能,自己用。 我们在用ReentrantLock加锁的时候都是调用的用lock()方法,那么我们来看看默认非公平锁下,lock()方法的源码:

/**
 * Sync object for non-fair locks
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

通过源码可以看到,lock()方法,首先是通过CAS的方式抢占锁,如果抢占成功则将state的值设置为1。然后将对象独占线程设置为当前线程。


protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

如果抢占锁失败,就会调用acquire()方法,这个acquire()方法的实现就是在AQS类中了,说明具体抢占锁失败后的逻辑,AQS已经规定好了模板。


public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

上面已经介绍了,独占模式是需要实现tryAcquire()方法的,这里首先就是通过tryAcquire()方法抢占锁,如果成功返回true,失败返回false。tryAcquire()方法的具体实现,是在ReentrantLock里面的,AQS类中默认是直接抛出异常的。

  • 首先获取state值,如果state值为0,说明无锁,那么通过CAS尝试加锁,成功后,将独占线程设置为当前线程。

  • 如果state值不为0,并且当前的独占线程和当前线程为同一线程,那么state重入次数加1。

  • 如果state值不为0,并且当前线程不是独占线程,直接返回false。


protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();// 获取state值
     if (c == 0) { 
     // 如果state值为0,说明无锁,那么就通过cas方式,尝试加锁,成功后将独占线程设置为当前线程
         if (compareAndSetState(0, acquires)) {
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     else if (current == getExclusiveOwnerThread()) { // 如果是同一个线程再次来获取锁,那么就将state的值进行加1处理(可重入锁的,重入次数)。
         int nextc = c + acquires;
         if (nextc < 0) // overflow
             throw new Error("Maximum lock count exceeded");
         setState(nextc);
         return true;
     }
     return false;
 }

我们继续来看acquire()方法,在执行完tryAcquire()方法后,如果加锁失败那么就会执行addWaiter()方法和acquireQueued(),这两个方法的作用是将竞争锁失败的线程放入到等待队列中。

image

image

addWaiter()方法的源码如下:


private Node addWaiter(Node mode) {
    // 用参数指定的模式将当前线程封装成队列中的节点(EXCLUSIVE【独占】,SHARED【共享】)
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    // tail是队列的尾部节点,初始时队列为空,尾部节点为null,直接调用enq将节点插入队列
    if (pred != null) {
    // 将当前线程节点的前级节点指向队列的尾部节点。
        node.prev = pred;
        // 通过CAS方式将节点插入到队列中
        if (compareAndSetTail(pred, node)) {
        // 插入成功后,将原先的尾部节点的后级节点指向新的尾部节点
            pred.next = node;
            return node;
        }
    }
    // 如果尾部节点为空或通过CAS插入队列失败则通过enq方法插入节点
    enq(node);
    return node;
}

addWaiter()中主要做了三件事:

  • 将当前线程封装成Node。

  • 判断队列中尾部节点是否为空,若不为空,则将当前线程的Node节点通过CAS插入到尾部。

  • 如果尾部节点为空或CAS插入失败则通过enq()方法插入到队列中。

那么enq()方法是又是怎么插入节点的呢?

enq()方法源码如下:


private Node enq(final Node node) {
    // 看到死循环,就明白是通过自旋咯
    for (;;) {
    // 当tail节点为空时直接将当前节点设置成尾部节点,并插入到队列中,以及设置它为head节点。
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
        // 若是因为在addWaiter()方法中插入失败或第二次进入循环,那么将当前线程的前级节点指向尾部节点,并通过CAS方式将尾部节点指向当前线程的节点。
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

其实enq()方法主要就是通过自旋将数据插入到队列中的操作:

  • 当队列为空时,将当前节点设置为头节点和尾节点。

  • 进入二次循环后,将node添加到尾部。

这样addWaiter()方法就构造了一个队列,并将当前线程添加到了队列中了。 我们再回到acquire()方法中。

image

image

现在就剩下acquireQueued()方法没看了,这个方法中的操作挺多的。


final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        // 获取前级节点,如果未null,则抛出异常
            final Node p = node.predecessor();
        // 如果前级节点为head,并且执行抢占锁成功。
            if (p == head && tryAcquire(arg)) {
            // 抢占锁成功,当前节点成功新的head节点
                setHead(node);
                // 然后将原先的head节点指向null,方便垃圾回收进行回收
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 如果当前节点不为head,或者抢占锁失败。就根据节点的状态决定是否需要挂起线程。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed) // 如果获取锁异常,则出取消获取锁操作。
            cancelAcquire(node);
    }
}
final Node predecessor() throws NullPointerException {
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}

  • 首先获取节点的前级节点。

  • 如果当前节点的前级节点是head那么就可以去抢占锁了。

  • 抢占成功后就将新节点设置为head,原先的head置为空。

  • 如果抢占锁失败,则根据waitStatus值决定是否挂起线程。

  • 最后,通过cancelAcquire()取消获取锁操作。

下面看一下shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()这两个方法是如何挂起线程的。


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;// 获取前级节点
    if (ws == Node.SIGNAL)// 如果前级节点的waitStatus值为SIGNAL(-1),说明当前节点也已经在等待唤醒了,直接返回true。
        return true;
  // 如果前级节点的waitStatus值大于0说明前级节点已经取消了。
    if (ws > 0) {
   // 如果前级节点已经是CANCEL状态了,那么会继续向前找,直到找到的节点不是CANCEL(waitStatue>0)状态的节点,然后将其设置为当前节点的前级节点。
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
    // 如果前级节点为0或者其他不为-1的小于0的值,则将当前节点的前级节点设置为 SIGNAL(-1)
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

parkAndCheckInterrupt()方法的作用就是挂起线程,如果shouldParkAfterFailedAcquire()方法成功,会执行parkAndCheckInterrupt()方法,它通过LockSupport的park()方法,将当前线程挂起(WAITING),它需要unpark()方法唤醒它,通过这样一种FIFO机制的等待,来实现Lock操作。


private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

LockSupport是JDK从1.6开始提供的一个线程同步源语工具类,在这里主要用到了它的两个方法,挂起线程和唤醒线程:


public static void park() {
    UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

LockSupport的挂起和唤醒线程都是不可重入的,它由一个许可标志,当调用park()时就会将许可设置为0,挂起线程,如果再调用一次park(),会阻塞线程。当调用unpark()时才会将许可标志设置成1。

ReentrantLock释放锁过程

ReentrantLock释放锁的过程主要有两个阶段:

  • 释放锁。

  • 唤醒挂起的线程。 unlock()方法的源码如下。


public void unlock() {
   sync.release(1);
}

释放锁的方法是写在父类,AbstractQueuedSynchronizer类中的。 源码如下:


// 释放独占模式下的锁资源
public final boolean release(int arg) {
    if (tryRelease(arg)) { // 尝试释放资源
        Node h = head;
  //释放成功后,判断头节点的状态是否为无锁状态,如果不为无锁状态就将头节点中的线程唤醒。
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false; // 释放资源失败,直接返回false
}

我们首先释放资源来看tryRelease()方法的源码,看看释放资源是怎样的过程。


protected final boolean tryRelease(int releases) {
// 从state中减去传入参数的相应值(一般为1)
    int c = getState() - releases;
    // 当释放资源的线程与独占锁现有线程不一致时,非法线程释放,直接抛出异常。
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 这里是处理重入锁的机制,因为可重入机制,所以每次都重入state值都加1,
    //所以在释放的时候也要相应的减1,直到state的值为0才算完全的释放锁资源。
    if (c == 0) {
        free = true;
        // 完全释放资源后,将独占线程设置为null,这样后面的竞争线程才有可能抢占。
        setExclusiveOwnerThread(null);
    }
    // 重新赋值state
    setState(c);
    return free;
}

tryRelease()方法在释放锁资源时,可以单纯的理解为是修改独占模式的状态值和置空占有线程的操作。将state的值减掉相应的参数值(一般是1),如果计算结果为0,就将他的独占线程设置为null,其他线程才有机会抢占成功。 在加锁时,同一线程加一次锁,state状态值就会加1,在解锁的时候没解锁一次就会减1,同一个锁可重入,只有lock次数与unlock次数相同才会释放资源,将独占线程设置为null。

释放了资源后,我们再看唤醒挂起线程时的过程。这个过程就在unparkSuccessor()方法中。


private void unparkSuccessor(Node node) {
    /* 获取当前节点的等待状态,一般是头节点,占有锁的节点是在头节点上。 */
    int ws = node.waitStatus;
    // 将当前节点的线程的状态值设为0,成为无锁状态。
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;// 获取下一个需要唤醒的节点线程。
    if (s == null || s.waitStatus > 0) {// 如果获取到的节点线程为空或已经取消
        s = null;
        // 就从队列的后面向前找,直到找到一个未取消的节点。
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null) // 如果获得的下一个可以唤醒的节点线程不为空,那么就唤醒它。
        LockSupport.unpark(s.thread);
}

这个释放过程就是将需要释放的线程节点设置成无锁状态,然后去队列中找到可以唤醒的节点,进行唤醒线程。 有一点需要解释一下,就是在寻找可以唤醒的节点时,为什么要从后向前找? 在上面unparkSuccessor()方法的源码里面有一段英文注释(7行~12行),我保留了下来了。

/* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */

这段英文注释翻译过来的大概意思就是:线程唤醒的时候,通常是从当前线程的下个节点线程开始寻找,但是下个节点有可能已经取消了或者为null了,所以从后向前找,直到找到一个非 取消状态的节点线程。

由于文章篇幅太长了,我这次就先将独占模式的加锁和解锁的过程总结到这里,下一篇通过CountDownLatch的加锁和解锁过程来总结AQS在共享模式下过程。

AQS共享模式分析

其实AQS的共享模式总结起来感觉比独占模式稍微容易一些,只是说总结起来稍微容易哦。

CountDownLatch的获取共享资源的过程

在使用CountDownLatch的时候,是先创建CountDownLatch对象,然后在每次执行完一个任务后,就执行一次countDown()方法。直到通过getCount()获取到的值为0时才算执行完,如果count值不为0可通过await()方法让主线程进行等待,知道所有任务都执行完成,count的值被设为0。 那么我们先来看创建CountDownLatch的方法。


public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

private static final class Sync extends AbstractQueuedSynchronizer {
   private static final long serialVersionUID = 4982264981922014374L;
   Sync(int count) {
       setState(count);
   }
}

我们看到创建CountDownLatch的过程,其实就是将count值赋值给state的过程。

再来看await()方法的源码:



public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);// 等待可中断的获取共享资源的方法
}
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
    if (Thread.interrupted()) // 如果线程已经中断,直接抛出异常结束。
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)// 尝试获取共享资源,获取失败后,自旋入队列
        doAcquireSharedInterruptibly(arg);// 可中断的入队列过程
}

整个await()的等待过程是,先尝试获取共享资源,获取成功则执行任务,获取失败,则调用方法自旋式进入等待队列。 通过最初在介绍AQS的时候就说过 ,共享模式下是需要自己去实现tryAcquireShared()方法来获取共享资源的,那么我们看看CountDownLatch是如何实现获取共享资源的。



protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

 

简单易懂,就一行代码,直接获取state值,等于0就是成功,不等于0就失败。

那么获取资源失败后,doAcquireSharedInterruptibly()方法是如何入执行的呢。 源码如下:



private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);// addWaiter()方法已经总结过了,这一步操作的目的就是将当前线程封装成节点加入队尾,并设置成共享模式。
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();// 获取前级节点
            if (p == head) {
            // 如果前级节点是头节点,直接尝试获取共享资源。
                int r = tryAcquireShared(arg);
                if (r >= 0) {// 如果获取共享资源成功,将head节点指向自己
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC 将原head节点指向空,方便垃圾回收。
                    failed = false;
                    return;
                }
            }
            // 如果不是前级节点不是head节点,就根据前级节点状态,判断是否需要挂起线程。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed) // 如果执行失败,取消获取共享资源的操作。
            cancelAcquire(node);
    }
}

这里的方法和独占模式下acquireQueued()方法很像,只是在设置头节点唤醒新线程的时候有所不同,在setHeadAndPropagate()方法里面。



private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
   // 如果在唤醒完下一个节点后,资源还有剩余,并且新唤醒的节点状态不为无效状态,就继续唤醒队列中的后面节点里的线程。
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

setHeadAndPropagate()这个方法名称翻译成中文是“设置头节点并传播”,其实就是在获取共享锁资源的时候,如果资源除了用于唤醒下一个节点后,还有剩余,就会用于唤醒后面的节点,直到资源被用完。这里充分体现共享模式的“共享”。

CountDownLatch释放资源

我们再来看countDown()方法是如何释放资源的。 源码如下:



public void countDown() {
    sync.releaseShared(1);
}

CountDownLatch中内部类Sync的releaseShared()方法,是使用的AQS的releaseShared()方法。



public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {// 尝试释放资源
        doReleaseShared();// 释放资源成功后,唤醒节点。
        return true;
    }
    return false;
}

尝试释放资源方法tryReleaseShared()是AQS规定需要自己来实现的,CountDownLatch的实现如下:



protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0) // 若state为0,说明已经不需要释放资源了,直接返回false。
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))// 真正的释放资源,是通过CAS的方式将state的值减1。
            return nextc == 0;
    }
}

其实主要的就是通过CAS的方式将state的值减1的操作。 释放资源成功后,就到了唤醒节点的过程了,在doReleaseShared()方法中。

private void doReleaseShared() {
  for (;;) {
      Node h = head;
      if (h != null && h != tail) {// 当头节点不为空,并且不等于尾节点时,从头开始唤醒。
          int ws = h.waitStatus;// 获取头节点的等待状态
          if (ws == Node.SIGNAL) {// 如果头节点状态为等待唤醒,那么将头节点的状态设置为无锁状态,若CAS设置节点状态失败,就自旋。
              if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                  continue;            // loop to recheck cases
              unparkSuccessor(h);// 唤醒头节点
          }// 如果head节点的状态已经为无锁状态了,那么将head节点状态设置为可以向下传播唤醒的状态(PROPAGATE)。
          else if (ws == 0 &&
                   !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
              continue;                // loop on failed CAS
      }
      // 若在执行过程中,head节点发生的变化,直接跳出循环。
      if (h == head)                   // loop if head changed
          break;
  }
}

至此,AQS的独占模式和共享模式,在获取共享资源和释放共享资源的过程,就总结完了。内容有点多,需要好好消化一下,能看到最后的也都厉害的人物,因为我自己在总结这部分内容的时候也是查阅了很多资料,看了很多源码,用了好几天的时间才自己总结明白,AQS到底是个什么东西,是怎么一个执行过程。

其实AQS里面不只我上面总结的这些内容,里面比如还有Condition、以及可中断的获取资源(acquireInterruptibly【独占】、acquireSharedInterruptibly【共享】acquire()和acquireShared()在线程等待过程中都是忽略中断的),还有ReentrantLock是如何实现公平锁的(其实是在竞争资源时如果有新进入的线程,先判断队列中是否有节点,如果有直接插入队尾等待,按顺序获取资源)。

通过总结了AQS,基于AQS实现的ReentrantLock、CountDownLatch、Semaphore等的源码基本上就能看懂了,甚至再上层的CyclicBarrier、CopyOnWriteArrayList我们通过看源码也能知道大概是一个什么过程了。

(面经总结)一篇文章带你完整复习 Java 中的 AQS 文章目录一、什么是AQS二、AQS的原理三、state:状态四、AQS共享资源的方式:独占式和共享式 一、什么是AQS AQS(Abstract Queued Synchronizer)是一个抽象的队列同步器,通过维护一个共享资源状态(Volatile Int State)和一个先进先出(FIFO)的线程等待队列来实现一个多线程访问共享资源的同步框架。 二、AQS的原理 AQS为每个共享资源都设置一个共享资源锁,线程在需要访问共享资源时首先需要获取共享资源锁,如果获取到了共享资源锁,便可以在当前线程中使用该 阅读详情

相关推荐

戴维南和诺顿定理到底怎么选?一个实际维修案例讲透两种等效电路用法

本文通过工业电源模块维修案例,详细解析戴维南和诺顿定理在实际电路分析中的选择策略。针对电压敏感型故障、电流分配问题及功率传输异常,提供三大黄金法则,帮助工程师高效选择等效电路方法,提升故障排查效率。特别强调诺顿定理在电流分析中的优势。

weixin_31854867的博客 357

你来讲讲AQS是什么吧?都是怎么用的?

前言 在Java面试的时候,多线程相关的知识是躲不掉的,肯定会被问。我就被问到了AQS的知识,就直接了当的问,AQS知道是什么吧,来讲讲它是怎么实现的,以及哪些地方用到了它。当时自己确实没有讲好,所以这次来总结一下这个知识点。 什么是AQS AQS全称是AbstractQueuedSynchronizer,形如其名,抽象队列同步器。 AQS定义了两种资源共享模式: 独占式,每次只能有一个线程持有锁,例如ReentrantLock实现的就是独占式的锁资源。 共享式,允许多个线程同时获取锁,并发访问共享资源,

Jimoer 1369

跨境TRS投资操作指南与系统解决方案

3 点击【申请 TRS 账户】,查看风险披露,录入账户参数、确定录入的参数信息,进行身份验证,输入验证码,TRS 账户开户申请已提交,后台对已提交的 TRS 开户申请进行审核,审核成功后,短信邮箱提醒,进行登录;2.点击【申请TRS账户】,查看风险披露,录入账户参数、确定录入的参数信息,进行身份验证,输入验证码,TRS账户开户申请已提交,后台对已提交的TRS开户申请进行审核,审核成功后,短信邮箱提醒,进行登录;4)更广泛的投资选择:相比A股通,TRS可以交易更多的A股标的,提供了更广泛的投资选择。

2401_85487119的博客 2852

面试必问的AQS(AbstractQueuedSynchronizer),一文全搞定

并发编程躲不掉的AQS

Sicimike的博客 3568

实战指南:在UE5中高效集成Entt(ECS)框架

本文提供了一份在虚幻引擎5中高效集成Entt ECS框架的实战指南。针对UE5项目在处理大规模动态实体时的性能瓶颈,详细讲解了如何通过数据驱动的ECS架构进行优化。内容包括前期环境准备、核心构建步骤、创建首个ECS子系统以及与UE原生系统的数据同步技巧,旨在帮助开发者快速掌握集成方法,提升游戏逻辑的执行效率。

weixin_29002209的博客 158

Ubuntu 26.04 本地安装 GitLab CE 完整教程(非 Docker 方式)

本文详细介绍了在Ubuntu 26.04系统上通过Omnibus包安装GitLab CE的完整流程。主要内容包括:环境准备(系统要求、依赖安装)、配置清华镜像源、执行安装命令、修改配置文件、服务管理命令以及常见问题解决方法。文章特别强调了内存要求(建议4GB以上)、首次登录密码获取、HTTPS安全配置等关键点,并提供了性能优化建议和安全设置指导。相比Docker方式,Omnibus安装具有性能更好、管理方便等优势,适合需要私有化部署GitLab的场景。

1373

Java多线程之AQS及其应用

AQS是一个抽象类,它为实现同步器提供了一个框架。它的核心思想是,如果需要实现一种同步器,只需要定义一个继承自AQS的类,实现它提供的一些抽象方法,就可以实现同步器了。AQS内部维护了一个FIFO的双向队列,用来存放被阻塞的线程。同时,它还有两个重要的状态量,分别是state和exclusiveOwnerThread。其中,state表示同步状态,exclusiveOwnerThread表示持有锁的线程。AQS提供了两种同步模式:独占模式和共享模式。

MRZHQ的博客 518

【鸿蒙游戏上架审核规范指南】

华为鸿蒙游戏上架审核规范指南摘要

weixin_44161102的博客 919

面试被问到什么是AQS,这样答满分

本篇内容基本已经涵盖了AQS的全部核心内容,本篇相比于上一篇补充了“中断”。

zhangjianfzf的博客 381

Dify使用教程(创建应用)

Dify的安装部署我已经写过了,简单的模型配置我也在前面进行了讲解,今天我们主要来讲讲如何使用Dify。

wal1314520的博客 1万+

10天冲刺国自然基金!用这套实战AI提示词指令教程,助你中标率翻倍!亲测DeepSeek和ChatGPT不好用

虽然以上国自然提示词指令经过反复实测,但并不是万能无脑帮你搞定所有,大模型AI的特性是你专业领域能力越强,它的智商表现就会更加强大,通过以上提示词教程的使用,大大提升中标概率是肯定的。还有就是大模型的使用上,不是市面上大家都说好的就一定适用,目前来看,Gemini和Claude的两个模型是非常适合国自然本子的修改的。在使用大模型进行国自然本子撰写修改时,首先是角色的设定,然后是目标和背景的描述,好用的提示词指令都是要经过无数次的实测,比对输出结果后又反复调整,达到最佳的效果状态的提示词指令。

智能AI让学术科研更容易 1247

面试官突击一问:你来讲讲AQS是什么吧?都是怎么用的

因为AbstractQueuedSynchronizer是一个抽象类,他采用模板方法的设计模式,规定了独占和共享模式需要实现的方法,并且将一些通用的功能已经进行了实现,所以不同模式的使用方式,只需要自己定义好实现共享资源的获取与释放即可,至于具体线程在等待队列中的维护(获取资源入队列、唤醒出队列等),AQS已经实现好了。 所以根据共享资源的模式一般实现的方法有如下几个: isHeldExclusively();// 是否为独占模式;但是只有使用到了Condition的,才需要去实现它。例如:Reentr

m0_69745415的博客 485

AE-扭曲-变形-弧

一些AE效果的着色器实现记录

What_can_you_do的博客 1175

【面试突击】并发编程、线程池面试实战

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术 的推送 发送 资料 可领取 深入理解 Redis 系列文章结合电商场景讲解 Redis 使用场景、中间件系列笔记和编程高频电子书! 文章导读地址:点击查看文章导读! 感谢你的关注!

qq_45260619的博客 1338

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

【2024Java面试突击】并发编程、线程池面试实战

最近在更新面试突击专栏,我把每一篇将字数都尽量控制在 2000 字以内,可能在文章里边写的没有那么细致,主要是提供一些 问题 以及 回答的思路 ,以及 面试中可能忽略的漏洞 ,所以在看完文章之后,如果自己简历中有这方面的内容的话,一定要认真去整理一份自己的回答,并且多查阅相关资料,如果看的文章少,就会导致学习到的内容太片面。

sinat_53467514的博客 1225

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Java面试高效突击:从原理到场景的实战解题框架

在软件开发领域,面试是评估开发者技术深度与解决问题能力的关键环节。其核心原理在于考察候选人是否具备将离散知识点关联成体系化知识网络,并应用于实际工程场景的能力。这种能力的技术价值在于,它能直接反映开发者面对复杂系统时的分析、设计与调试水平,是区分普通编码者与高级工程师的重要标尺。典型的应用场景包括系统性能调优、高并发设计、线上故障排查以及分布式架构决策。本文聚焦于Java技术栈,深入剖析如何将Java基础、并发编程、JVM、MySQL及Spring等核心模块的知识点,通过建立诸如从HashMap原理到Con

weixin_30562507的博客 350

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Java面试突击:高频考点深度解析与实战策略

在Java技术生态中,掌握核心原理是构建高效、稳定应用系统的基石。从数据结构与算法到并发编程,再到JVM内存管理与垃圾回收机制,理解这些底层原理能帮助开发者编写高性能、线程安全的代码。例如,深入理解HashMap的数组+链表/红黑树结构、扩容机制及线程安全问题,不仅是应对面试的关键,更是解决实际高并发场景下数据一致性与性能瓶颈的必备知识。同样,对JVM内存模型、垃圾回收器(如G1、ZGC)及MySQL索引(B+树)、事务隔离级别(如可重复读下的MVCC)的掌握,直接关系到线上系统的稳定性和扩展性。这些技术广

weixin_34320159的博客 383

Java面试核心知识点与高效突击方法论

Java面试作为技术岗位的重要考核环节,其核心考察点往往集中在JVM、并发编程、集合框架和Spring生态等关键技术领域。理解这些技术原理不仅有助于面试准备,更是提升工程实践能力的关键。JVM内存模型和垃圾回收机制是Java性能优化的基础,而并发编程中的synchronized和AQS框架则是解决多线程问题的核心工具。集合框架如HashMap的底层实现直接关系到数据处理的效率,Spring生态的Bean管理和事务机制则是企业级开发的基石。通过构建知识图谱和三轮复习法,开发者可以在短时间内高效掌握这些高频考点

weixin_30271335的博客 296

Java面试突击:从死记硬背到构建问题解决框架的高效策略

在软件开发领域,面试准备常被视为知识点的单向积累,但更本质的是信息的高效组织与检索能力。其核心原理在于构建一个结构化的知识索引系统,而非无限制地扩充记忆容量。这一方法的技术价值在于,它能将零散的技术概念(如Java内存模型、并发编程)串联成可推导的知识网络,从而在高压面试场景下实现快速、准确的响应。从应用场景看,无论是应对高频的JVM性能排查、MySQL索引优化,还是Spring框架的深度原理追问,构建以“母题”为核心、以“场景-技术-原理”为应答结构的学习框架,都能显著提升工程师的表达深度与问题拆解能力。

weixin_34228387的博客 361

【面试突击】Java面试底层逻辑(HashMap、ConcurrentHashMap面试实战)

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术 的推送 发送 资料 可领取 深入理解 Redis 系列文章结合电商场景讲解 Redis 使用场景、中间件系列笔记和编程高频电子书! 文章导读地址:点击查看文章导读! 感谢你的关注!

qq_45260619的博客 1389

Java面试突击:结构化突击与降维打击,7天构建高效知识体系

在Java技术领域,构建一个清晰、可执行的知识体系是应对中高级岗位面试的关键。其核心原理在于将散点化的技术知识点(如JVM、并发编程、MySQL索引)通过场景化思维进行结构化串联,形成从概念理解到实战应用的完整闭环。这种结构化学习方法的技术价值在于,它能将庞大的知识体系压缩成高效的突击路径,帮助开发者从被动记忆转向主动构建解题框架。在实际应用场景中,例如设计一个高并发的秒杀系统,就需要将Redis的原子操作、消息队列的削峰填谷、线程池的参数优化等核心层知识,与系统分层设计、限流降级等场景层思维有机结合。通过

weixin_33991418的博客 332

Java面试高效突击:一周掌握核心考点与场景串联法

在软件开发领域,掌握核心原理和高效学习方法是应对技术挑战的关键。从计算机科学基础概念出发,理解数据结构、算法和设计模式是构建复杂系统的基石。深入原理层面,例如理解JVM内存模型、并发编程的线程安全机制以及数据库的索引与事务隔离级别,能够帮助开发者编写高性能、高可用的应用。这些技术的价值在于解决实际工程中的性能瓶颈、数据一致性及系统扩展性问题,广泛应用于电商、金融等高并发场景。本文聚焦于Java技术栈,通过“场景串联法”和“高频问题树”等策略,将零散的知识点如并发编程中的volatile关键字和JVM调优中的

weixin_33701294的博客 394

Java面试突击:2-4周攻克高频考点与场景设计

在软件开发领域,面试是评估工程师技术能力的关键环节,尤其对于Java技术栈而言,其知识体系庞大且更新迅速。理解面试的核心原理在于,它不仅考察对基础概念的掌握,更注重将原理应用于解决实际工程问题的能力。这种考察方式的技术价值在于,能够高效筛选出具备扎实基本功和良好工程思维的候选人。在Java开发者的求职场景中,面对时间紧迫的“金九银十”等招聘季,如何系统化、高效率地准备面试,构建覆盖核心考点的知识体系,成为一项普遍需求。本文聚焦于**Java面试突击**与**高频考点**,提供一套可执行的短期复习方案,帮助开

weixin_29304985的博客 265

Java面试突击:从知识点串联到场景化应答的实战指南

在软件工程领域,面试准备常面临知识点分散、难以串联的挑战。理解计算机科学的核心原理,如数据结构、算法、系统设计,是构建扎实技术能力的基础。这些原理通过具体的技术栈(如Java并发模型、JVM内存管理、数据库索引机制)实现其工程价值,能够有效解决高并发、系统性能优化等实际问题。在实际应用场景中,例如构建秒杀系统或排查线上故障,需要将分散的知识点(如线程池参数调优、MySQL的EXPLAIN执行计划分析)进行有机整合与场景化应用。本文聚焦于**Java面试突击**与**场景题应答框架**,提供一套从核心模块重点

weixin_26795617的博客 199

Java面试突击:从散点记忆到体系构建的高效备战策略

在软件开发领域,构建扎实的知识体系是工程师应对复杂技术挑战的核心能力。其原理在于将离散的技术点通过逻辑关联形成网络化认知,从而提升问题分析与解决效率。这种体系化思维的技术价值在于,它能帮助开发者从记忆知识点升级为理解技术脉络,在面对综合性场景时快速定位并串联解决方案。尤其在Java技术栈中,高频的面试场景如高并发处理、JVM性能调优、数据库优化等,都要求候选人不仅能回答“是什么”,更能阐述“如何关联”与“为何如此”。本文聚焦于Java面试准备,通过“问题-知识-场景”三维框架,将核心的并发编程、JVM机制、

452
上一篇: 面试官:你说一下对Java中的volatile的理解吧!
下一篇: 面试手撕-分布式技术:限流、通讯、缓存,全部一锅端走送给你!
加班不秃顶
博客等级 码龄7年 1822粉丝 599原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值