超时引发的牛角尖二(hystrix中的熟悉面孔)

文章讲述了作者在解决系统中关于超时问题时的反思,从误解Hystrix中的超时概念,转而深入探讨了Java并发编程中的CountDownLatch和ExecutorService,特别是它们如何处理线程同步和任务执行的超时问题。

首先跟大家道个歉,本篇文章原题为《超时引发的牛角尖二(hystrix中的超时)》,经人指导,我认识到了自己的无知:hystrix无法感知到超时,所以讨论hystrix中的超时,本身就是错误的。故将本篇标题更改为《超时引发的牛角尖(hystrix中的熟悉面孔)》。在此本人真诚欢迎大家指出文章中的错误!

至今我都清楚记得自己负责的系统请求云上关联系统时所报的异常信息。为了解决这个异常,我坚持让这个关联系统的负责人查看,并且毫不顾忌他的嘲讽和鄙视,甚至无视他烦躁的情绪。不过我还是高估了自己的脸皮,最终在其恶狠狠地抛下“你自己的问题为啥不自己看!”这句话后悻悻离开。当时,这个“超时”问题就铭刻在了我的脑海里。

回到座位,我就狠地翻起了代码,最终发现我们系统调用他们系统地请求会被包装到HystrixCommand子类对象中,然后通过调用该对象上地execute()方法来完成。但是我依旧没有弄明白超时是怎么实现的。直到今天,我才有了一个大概的思路。为了将自己的思路描述出来,先让我们看一些hystrix中用到的工作中经常见到的工具类吧。

1. CountDownLatch

首先要介绍的就是CountDownLatch。它是java并发包(java.util.concurrent)中提供的一个同步工具类,其允许一个或多个线程等待其他线程完成一组操作后再执行。它在多线程编程中主要用于实现线程间的协调和同步。接下来让我们看一下其源码:

public class CountDownLatch {
    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

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

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;

    /**
     * Constructs a {@code CountDownLatch} initialized with the given count.
     *
     * @param count the number of times {@link #countDown} must be invoked
     *        before threads can pass through {@link #await}
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    /**
     * Causes the current thread to wait until the latch has counted down to
     * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
     *
     * <p>If the current count is zero then this method returns immediately.
     *
     * <p>If the current count is greater than zero then the current
     * thread becomes disabled for thread scheduling purposes and lies
     * dormant until one of two things happen:
     * <ul>
     * <li>The count reaches zero due to invocations of the
     * {@link #countDown} method; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread.
     * </ul>
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * @throws InterruptedException if the current thread is interrupted
     *         while waiting
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * Causes the current thread to wait until the latch has counted down to
     * zero, unless the thread is {@linkplain Thread#interrupt interrupted},
     * or the specified waiting time elapses.
     *
     * <p>If the current count is zero then this method returns immediately
     * with the value {@code true}.
     *
     * <p>If the current count is greater than zero then the current
     * thread becomes disabled for thread scheduling purposes and lies
     * dormant until one of three things happen:
     * <ul>
     * <li>The count reaches zero due to invocations of the
     * {@link #countDown} method; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     * <li>The specified waiting time elapses.
     * </ul>
     *
     * <p>If the count reaches zero then the method returns with the
     * value {@code true}.
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * <p>If the specified waiting time elapses then the value {@code false}
     * is returned.  If the time is less than or equal to zero, the method
     * will not wait at all.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the {@code timeout} argument
     * @return {@code true} if the count reached zero and {@code false}
     *         if the waiting time elapsed before the count reached zero
     * @throws InterruptedException if the current thread is interrupted
     *         while waiting
     */
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    /**
     * Decrements the count of the latch, releasing all waiting threads if
     * the count reaches zero.
     *
     * <p>If the current count is greater than zero then it is decremented.
     * If the new count is zero then all waiting threads are re-enabled for
     * thread scheduling purposes.
     *
     * <p>If the current count equals zero then nothing happens.
     */
    public void countDown() {
        sync.releaseShared(1);
    }

    /**
     * Returns the current count.
     *
     * <p>This method is typically used for debugging and testing purposes.
     *
     * @return the current count
     */
    public long getCount() {
        return sync.getCount();
    }

    /**
     * Returns a string identifying this latch, as well as its state.
     * The state, in brackets, includes the String {@code "Count ="}
     * followed by the current count.
     *
     * @return a string identifying this latch, as well as its state
     */
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

从源码可以看出,CountDownLatch内部维护了一个计数器(counter),该计数器在构造时初始化为一个特定的值。其中有两个比较常用的方法:1.countDown()方法,当一个线程完成了自己的工作后调用此方法,会将计数器减1;2.await()方法,其会在主线程或者其他等待线程中调用,如果当前计数器不为0,则会阻塞等待,直到计数器递减至0为止。此外还有一个与await()方法作用类似的方法——await(long timeout, TimeUnit unit),该方法可以实现超时等待。在这个方法中,我们会传入一个等待的超时时间(timeout)和时间单位(TimeUnit)。如果在指定的时间内计数器到达0或者当前线程被中断,则该方法会返回true。如果计数器没有到达0并且等待时间超过了指定的超时时间,那么该方法将会返回false。下面让我们看一个例子:

public class SpringTransactionApplication {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        new Thread() {
            @Override
            public void run() {
                try {
                    // 当前线程等待 10 秒钟
                    if (!countDownLatch.await(3, TimeUnit.SECONDS)) {
                        System.out.println("超时");
                        return;
                    } else {
                        System.out.println("正常");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }.start();
        // 主线程休眠 8 秒钟
        try {
            Thread.sleep(1000 * 8);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 主线程调用 CountDownLatch 上的 countDown() 方法
        countDownLatch.countDown();
        // 这里会有两种情况,如果主线程 - main 的等待时间超过了当前线程 - t1 的等待时间,那么当前线程 - t1 会输出“超时“
        // 如果主线程 - main 的等待时间小于当前线程 - t1 的等待时间,则当前线程 - t1 会输出“正常”

    }

}

通过执行这个例子,我们不难发现,在主线程等待8秒的时间里,没有任何线程调用CountDownLatch上的countDown()方法,所以线程t1在等待3秒后await(timeout, TimeUnit)方法直接返回了false,所以可以看到控制台输出了超时,如下图所示:

e16abc3d6a3a456f8ff7737b051a9822.png

下面让我们一起梳理一下这个类的应用场景吧:

  1. 启动屏障:主线程等待多个子线程完成初始化或任务后才能继续执行。例如,在一个服务启动时,主线程需要等待所有组件和服务加载完毕。主线程创建一个CountDownLatch,并设置计数器为组件数量,每个组件加载完成后调用countDown()方法,当计数器归零时,主线程通过await()方法解除阻塞。
  2. 一次性活动:所有参与者准备好后开始执行一次性的活动,如比赛开始。想象一个学生跑步比赛的例子,所有参赛者(线程)准备就绪后,裁判(主线程)等待所有选手(通过CountDownLatch)表示准备完毕,然后鸣枪开始比赛。
  3. 批处理任务同步:在分布式系统中,可能需要等待多个独立的异步任务完成后再进行下一步操作。比如,你可能有一个工作线程池处理大量任务,主线程使用CountDownLatch来等待所有这些任务结束。
  4. 性能测试:在压力测试或性能基准测试中,可以用来确保所有并发线程都已开始执行任务后才开始测量时间,最后再等待所有线程完成以计算总耗时。
  5. 阶段控制:多阶段任务中,某个阶段需要等待前一阶段的所有工作全部完成。例如,在构建过程中,等待所有编译任务结束后再统一进行部署。
  6. 资源初始化:当某些共享资源必须在多个线程能够访问之前先完成初始化时,可以通过CountDownLatch来实现同步。

总的来看,CountDownLatch通常用于解决一个或多个线程必须等待一组其他线程完成各自的工作之后才能继续执行的问题。(仔细审视我们的超时案例,其编程模式与CountDownLatch通常要解决的问题一致)

2.Executor及其子类

Executor是java并发编程框架中的一个核心接口,位于java.util.concurrent包中。它定义了一种统一的方式来执行异步任务,即实现了Executor的对象能够接收Runnable对象作为任务,并负责安排这些任务在某一时刻执行。下面是Executor接口的源码:

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

从上述源码可以看出,execute()方法会接收一个Runnable类型的任务参数。该任务将在Executor管理的线程中异步执行。通过这个接口,我们可以将关注点从如何创建和管理线程转移到如何定义和提交执行的任务上,从而简化了多线程编程模型,提高了程序的可读性和可维护性。另外,java提供了Executors工具类,它是一个静态工厂类,提供了多种预配置的线程池实现,比如固定大小的线程池、单线程执行器、可缓存线程池等等,这些都是ExecutorService接口(该接口继承了Executor接口)的实现,进一步增强了对线程池功能的支持,包括任务调度、线程生命周期管理以及任务结果的获取等。下面就让我们一起来研究一下java并发包提供的一个扩展自Executor 接口的重要接口(ExecutorService)吧。它提供了更多管理和控制线程池的方法。ExecutorService 接口提供了启动、执行和关闭线程池的能力,以及对异步任务的更丰富的控制,比如可以取消正在执行的任务,查询线程池状态,提交具有返回值的任务等。该接口中的主要方法包括:

  1. submit(Callable<T> task): 提交一个有返回值的任务,返回一个 Future 对象,通过该对象可以获取任务执行结果或者取消任务。
  2. submit(Runnable task, T result): 提交一个无返回值的任务,并提供一个结果,返回一个 Future 对象。
  3. execute(Runnable command): 执行一个 Runnable 类型的任务,与 Executor 接口中的方法一致。
  4. shutdown(): 关闭 ExecutorService,不再接收新任务,但会将已提交的任务执行完毕。
  5. shutdownNow(): 尝试停止所有正在执行的任务并返回尚未开始的任务列表。
  6. awaitTermination(long timeout, TimeUnit unit): 等待所有任务都已完成,或者超过指定时间后终止等待。
  7. invokeAll(Collection<? extends Callable<T>> tasks): 执行给定的任务集合,当所有任务完成或超时后返回包含每个任务结果的 Future 列表。
  8. invokeAny(Collection<? extends Callable<T>> tasks): 执行给定的任务集合,返回第一个成功完成的任务的结果。

通过 ExecutorService,我们可以更加方便地管理和控制并发任务,有效地利用系统资源,提高程序的性能和响应速度等。下面让我们一起来看一下这个接口的继承体系:

091d3ea3dfc64c4794238ce693008967.png

通过上面这张图片,我们能够清晰的看到面试中的靓仔ThreadPoolExecutor继承自AbstractExecutorService,从而间接实现了Executor接口。它就是我们通常讲的线程池,该Executor实现允许自定义核心线程数、最大线程数、线程存活时间、任务队列、线程工厂以及拒绝策略等。这里我还有一个疑问:线程池中任务的执行逻辑是什么?

  1. 提交任务:调用线程池对象(通常是ThreadPoolExecutor)的execute(Runnable task)方法提交一个任务。接着执行下述逻辑:
  2. 核心线程数检查:如果当前运行的线程数量小于核心线程数(coolPoolSize),则创建一个新的工作线程来执行该任务;如果当前线程数等于核心线程数,或者新提交的任务到来时已经有足够的核心线程正在运行,则进入下一步:
  3. 工作队列处理:将任务添加到工作队列(通常为BlockingQueue<Runnable>实现),让空闲的核心线程从队列中取出并执行;如果工作队列未满,那么任务会被放入队列中等待执行,否则继续下一步
  4. 最大线程数检查:如果当前线程数小于最大线程数(maxmumPoolSize),并且工作队列已满,则创建新的非核心线程来执行这个任务;如果当前线程数已经达到最大线程数并且无法将任务加入队列,则触发饱和策略(RejectedExecutionHandler)
  5. 包和策略执行:默认的饱和策略是AbortPolicy,即抛出RejectedExecutionException类型的异常,也可以自定义饱和策略,比如CallerRunsPolicy,即提交任务的线程自己执行这个任务;DiscardPolicy,即直接丢弃新提交的任务,不抛出异常也不执行;DicardOldestPolicy,即丢弃工作队列中最旧的任务,然后尝试重新提交任务
  6. 任务执行和回收:线程执行完任务后,会从工作队列中获取下一个任务,如果队列是空的,则线程可能会变成空闲状态,并根据线程池的配置决定是否要关闭或保留当前的这个线程(比如,若设置了超时时间,空闲超过一定时间的线程会被终止)

首先跟大家说声抱歉,今天无法完成本篇文章的主旨了,明天我会继续就文章主旨进行梳理。

 

 

 

 

下载代码方式:https://pan.quark.cn/s/a4b39357ea24 依据所提供的资料,我们深入剖析此问题以及所给出的两种算法方案。 ### 问题背景 该问题源自王晓东编撰的《算法设计与实验题解》一书,书中阐述了一个值得注意的数学议题:针对一本页码从1到n顺序编号的书籍,要求统计所有页码中数字0至9各自出现的频次。例如,若n=13,则页码序列为1、2、...、13,其中数字1出现5次(体现在1、10、11、12、13中),数字0出现1次(体现在10中)。 ### 问题描述 具体而言,我们需要开发一种算法,其输入参数为一个正整数n,输出结果需为0至9这十个数字各自出现的频次。所有页码均以十进制形式呈现,且不包含任何前导零,即不会出现如006之类的页码表示。 ### 解决方案一:时间复杂度为O(n*log10(n))的算法 首先,介绍一种时间复杂度为O(n*log10(n))的算法实现。其核心构思在于遍历从1到n的每一个数值,然后逐一分解每个数值的各个位,并统计各类数字出现的频次。具体步骤如下: 1. 初始化一个长度为10的数组`count`,用于记录0至9每个数字出现的频次,初始值均为0。 2. 从1开始遍历至n,对于每一个数值i,将其转换为整数并进行以下操作: - 利用循环结构,持续将当前数值除以10,获取余数(即当前最低位的数字),并累加到对应的计数器中。 3. 遍历完成后,输出`count`数组中的每一个元素,即为所求的结果。 ### 解决方案:优化算法 为了提升效率,提出了一种更为优越的算法。该算法基于以下观察:在1到10^n-1之间的任意区间内,每一种数字0至9出现的频次是相等的。例如,在1到999之间,每一种数字0至9出现的频次均相...
内容概要:本文档详细介绍了基于直驱永磁同步发电机(PMSG)的1.5MW风力发电系统在Simulink环境下的建模与仿真方法,涵盖风力机、传动系统、PMSG本体及电力电子变换器等核心组件的数学建模与系统集成。通过构建完整的风电系统仿真平台,实现了对风速扰动、机械动力学响应、电磁能量转换及并网运行特性的动态模拟,重点解析了PMSG在不同工况下的运行行为与先进控制策略的设计与实现,如最大功率点跟踪(MPPT)和矢量控制技术。该模型不仅可用于风电系统的性能评估与优化,还可作为控制器设计与算法验证的有效工具,支持新能源领域的教学、科研与工程应用。; 适合人群:具备电力系统、电机控制或可再生能源发电等相关背景的科研人员、工程技术人员及高校研究生;熟悉MATLAB/Simulink仿真环境者尤佳。; 使用场景及目标:①开展风力发电系统的动态特性分析与先进控制策略研究;②完成课程设计、学位论文或科研项目中的系统建模任务;③复现高水平学术论文中的风电仿真案例,支撑科研成果的验证与发表。; 阅读建议:建议结合文档中提到的相关控制算法与优化策略进行拓展学习,重点关注模型结构搭建、参数配置与仿真调试过程,并通过改变风速输入、负载条件等变量开展多工况仿真实验,深入理解系统动态响应机制与控制效果。
内容概要:本文系统研究了基于粒子群PSO、灰狼GWO、鲸鱼WOA、哈里斯鹰HHO、蜣螂DBO、麻雀SSA等多种智能优化算法的无人机三维路径规划方法,利用Matlab代码实现了在复杂三维环境下的路径搜索与避障功能,并构建包含路径长度、飞行高度、障碍物规避、转弯代价等多维度的综合成本函数体系,对各算法的收敛速度、寻优能力、路径平滑性及全局搜索性能进行了定量对比分析。研究不仅展示了各类群智能算法在路径规划中的实现机制与参数敏感性,还提供了可复现的仿真平台,为无人机自主导航系统的开发与优化提供了理论依据和技术支撑。; 适合人群:具备Matlab编程基础和基本优化算法知识,从事无人机路径规划、智能控制、自动化、机器人技术等相关领域的科研人员、工程技术人员及高校研究生。; 使用场景及目标:① 对比分析主流群智能优化算法在复杂三维空间路径规划中的性能差异与适用条件;② 构建并优化多目标成本函数以提升路径规划的安全性与经济性;③ 为科研项目、学术论文撰写或实际工程应用提供可靠、可复现的Matlab代码参考与仿真框架; 阅读建议:建议读者结合所提供的Matlab代码逐模块调试运行,深入理解各算法的迭代机制与路径生成过程,重点关注参数设置对优化结果的影响,并可根据具体应用场景调整环境建模与成本权重,进一步拓展和优化算法性能。
内容概要:本文围绕“风光制氢合成氨系统优化研究”展开,详细介绍了利用Python代码对该综合能源系统进行建模与优化的全过程。通过复现高水平学术论文,构建了集成风能、光伏等可再生能源的制氢及合成氨系统模型,充分考虑了可再生能源出力的随机性与波动性、关键设备运行的技术约束以及系统整体的经济性目标,采用先进的数学优化算法对系统的容量配置与运行调度策略进行联合求解,旨在提升绿氢与绿氨生产的效率,促进可再生能源的高效消纳并推动工业领域深度脱碳。文中提供了完整的Python代码实现方案,涵盖数据处理、模型构建、求解器调用与结果可视化等环节,具有较强的可复现性和次开发价值。; 适合人群:具备一定Python编程基础和优化建模能力,从事新能源系统规划、综合能源系统优化、绿色化工、电力系统调度及相关领域的科研人员、工程技术人员和高校研究生。; 使用场景及目标:①深入学习并复现风光耦合电解水制氢与合成氨的集成系统优化模型;②掌握基于Python的能源系统建模、多目标优化与不确定性处理方法;③应用于绿色氨生产系统设计、可再生能源大规模消纳、低碳工业流程优化等前沿科研与工程项目。; 阅读建议:建议读者结合文中提供的完整代码,使用实际气象与负荷数据进行调试与验证,深入理解目标函数的构建逻辑、各类物理与运行约束的数学表达以及优化求解器(如Pyomo+CBC或Gurobi)的具体应用,进而可拓展至考虑更多不确定性因素(如价格波动)或多能互补(如储能)的复杂场景研究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

机器挖掘工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值