阿里的面试题带你认识ForkJoinPool

关于Java中ForkJoinPool.commonPool()详解 jdk8中的并行流parallelStream,CompletableFuture异步编程中有没有用到线程池? ForkJoinPool.commonPool()是什么?它的参数是固定的?还是程序可干预的? 阅读详情

我相信大家都用过线程池,比如ExcutorService,比如ThreadPoolExcutor

今天来讲讲ForkJoinPool,它实现于ExcutorService,但又和我们常用的

ThreadPoolExcutor原理不同

前言

随着在硬件上多核处理器的发展和广泛使用,并发编程成为程序员必须掌握的一门技术,在面试中也经常考查面试者并发相关的知识。

今天,我们就从一道阿里的面试题来开始

题目:如何充分利用多核CPU,计算超大数组中所有整数的和?

解析开始

  • 1.单线程相加?

我们最容易想到就是单线程相加,一个for循环搞定。

  • 2.线程池相加?

如果进一步优化,我们会自然而然地想到使用线程池来分段相加,最后再把每个段的结果相加。

  • 3.其它?

Yes,就是我们今天的主角——ForkJoinPool,但是它要怎么实现呢?似乎没怎么用过哈^^

让我们看看上面是那种方法都如何实现

/**
 * 计算1亿个整数的和
 */
public class ForkJoinPoolTest01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 构造数据
        int length = 100000000;
        long[] arr = new long[length];
        for (int i = 0; i < length; i++) {
            arr[i] = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);
        }
        // 单线程
        singleThreadSum(arr);
        // ThreadPoolExecutor线程池
        multiThreadSum(arr);
        // ForkJoinPool线程池
        forkJoinSum(arr);

    }

    private static void singleThreadSum(long[] arr) {
        long start = System.currentTimeMillis();

        long sum = 0;
        for (int i = 0; i < arr.length; i++) {
            // 模拟耗时,本文由公从号“彤哥读源码”原创
            sum += (arr[i]/5*5/5*5/5*5/5*5/5*5);
        }

        System.out.println("sum: " + sum);
        System.out.println("single thread elapse: " + (System.currentTimeMillis() - start));

    }

    private static void multiThreadSum(long[] arr) throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        int count = 8;
        ExecutorService threadPool = Executors.newFixedThreadPool(count);
        List<Future<Long>> list = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            int num = i;
            // 分段提交任务
            Future<Long> future = threadPool.submit(() -> {
                long sum = 0;
                for (int j = arr.length / count * num; j < (arr.length / count * (num + 1)); j++) {
                    try {
                        // 模拟耗时
                        sum += (arr[j]/5*5/5*5/5*5/5*5/5*5);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return sum;
            });
            list.add(future);
        }

        // 每个段结果相加
        long sum = 0;
        for (Future<Long> future : list) {
            sum += future.get();
        }

        System.out.println("sum: " + sum);
        System.out.println("multi thread elapse: " + (System.currentTimeMillis() - start));
    }

    private static void forkJoinSum(long[] arr) throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
        // 提交任务
        ForkJoinTask<Long> forkJoinTask = forkJoinPool.submit(new SumTask(arr, 0, arr.length));
        // 获取结果
        Long sum = forkJoinTask.get();

        forkJoinPool.shutdown();

        System.out.println("sum: " + sum);
        System.out.println("fork join elapse: " + (System.currentTimeMillis() - start));
    }

    private static class SumTask extends RecursiveTask<Long> {
        private long[] arr;
        private int from;
        private int to;

        public SumTask(long[] arr, int from, int to) {
            this.arr = arr;
            this.from = from;
            this.to = to;
        }

        @Override
        protected Long compute() {
            // 小于1000的时候直接相加,可灵活调整
            if (to - from <= 1000) {
                long sum = 0;
                for (int i = from; i < to; i++) {
                    // 模拟耗时
                    sum += (arr[i]/5*5/5*5/5*5/5*5/5*5);
                }
                return sum;
            }

            // 分成两段任务,本文由公从号“彤哥读源码”原创
            int middle = (from + to) / 2;
            SumTask left = new SumTask(arr, from, middle);
            SumTask right = new SumTask(arr, middle, to);

            // 提交左边的任务
            left.fork();
            // 右边的任务直接利用当前线程计算,节约开销
            Long rightResult = right.compute();
            // 等待左边计算完毕
            Long leftResult = left.join();
            // 返回结果
            return leftResult + rightResult;
        }
    }
}

~~Garnett偷偷地告诉你,实际上计算1亿个整数相加,单线程是最快的,我的电脑大概是100ms左右,使用线程池反而会变慢。~~

~~所以,为了演示ForkJoinPool的牛逼之处,我把每个数都/5*5/5*5/5*5/5*5/5*5了一顿操作,用来模拟计算耗时。~~

来看结果:

sum: 107352457433800662
single thread elapse: 789
sum: 107352457433800662
multi thread elapse: 228
sum: 107352457433800662
fork join elapse: 189

可以看到,ForkJoinPool相对普通线程池还是有很大提升的。

什么是ForkJoinPool?

谈到线程池,很多人会想到Executors提供的一些预设的线程池,比如单线程线程池SingleThreadExecutor,固定大小的线程池FixedThreadPool,但是很少有人会注意到其中还提供了一种特殊的线程池:WorkStealingPool,我们点进这个方法,会看到和其他方法不同的是,这种线程池并不是通过ThreadPoolExecutor来创建的,而是ForkJoinPool来创建的

public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

这两种线程池之间并不是继承关系,而是平级关系:

ThreadPoolExecutor应该都很了解了,就是一个基本的存储线程的线程池,需要执行任务的时候就从线程池中拿一个线程来执行。而ForkJoinPool则不仅仅是这么简单,同样也不是ThreadPoolExecutor的代替品,这种线程池是为了实现“分治法”这一思想而创建的,通过把大任务拆分成小任务,然后再把小任务的结果汇总起来就是最终的结果,和MapReduce的思想很类似

最核心的思想可以这样描述:

if(任务很小){
    直接计算得到结果
}else{
    分拆成N个子任务
    调用子任务的fork()进行计算
    调用子任务的join()合并计算结果
}
  • 1.fork()

fork()方法类似于线程的Thread.start()方法,但是它不是真的启动一个线程,而是将任务放入到工作队列中。

  • 2.join()

join()方法类似于线程的Thread.join()方法,但是它不是简单地阻塞线程,而是利用工作线程运行其它任务。当一个工作线程中调用了join()方法,它将处理其它任务,直到注意到目标子任务已经完成了。

ForkJoinPool内部原理-工作窃取

work-stealing(工作窃取)算法

ForkJoinPool 的另一个特性是它使用了work-stealing(工作窃取)算法:

线程池内的所有工作线程都尝试找到并执行已经提交的任务,或者是被其他活动任务创建的子任务(如果不存在就阻塞等待)。这种特性使得 ForkJoinPool 在运行多个可以产生子任务的任务,或者是提交的许多小任务时效率更高。尤其是构建异步模型的 ForkJoinPool 时,对不需要合并(join)的事件类型任务也非常适用。

在 ForkJoinPool 中,线程池中每个工作线程(ForkJoinWorkerThread)都对应一个任务队列(WorkQueue),工作线程优先处理来自自身队列的任务(LIFO或FIFO顺序,参数 mode 决定),然后以FIFO的顺序随机窃取其他队列中的任务。

ForkJoinPool中的任务

ForkJoinPool 中的任务分为两种:

一种是本地提交的任务(Submission task,如 execute、submit 提交的任务);

另外一种是 fork 出的子任务(Worker task)。

两种任务都会存放在 WorkQueue 数组中,但是这两种任务并不会混合在同一个队列里,ForkJoinPool 内部使用了一种随机哈希算法(有点类似 ConcurrentHashMap 的桶随机算法)将工作队列与对应的工作线程关联起来,Submission 任务存放在 WorkQueue 数组的偶数索引位置,Worker 任务存放在奇数索引位。

实质上,Submission 与 Worker 一样,只不过它被限制只能执行它们提交的本地任务,在后面的源码解析中,我们统一称之为“Worker”。


任务的分布情况如下图:

ForkJoinPool原理

初始化ForkJoinPool

ForkJoinPool pool = ForkJoinPool.commonPool()

public static ForkJoinPool commonPool() {
    // assert common != null : "static init error";
    return common;
}

获取ForkJoinPool很简单,直接调用commonPool()。注意,这个方法是jdk1.8才加的,也是推荐的方法,满足大部分场景。

static{
    //...
    common = java.security.AccessController.doPrivileged
            (new java.security.PrivilegedAction<ForkJoinPool>() {
                public ForkJoinPool run() { return makeCommonPool(); }});
    //...
}

private static ForkJoinPool makeCommonPool() {
    //...
    return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE,"ForkJoinPool.commonPool-worker-");
}

common在static{}里创建,调用的是makeCommonPool(),最终调用ForkJoinPool的构造函数。

private ForkJoinPool(int parallelism,
                     ForkJoinWorkerThreadFactory factory,
                     UncaughtExceptionHandler handler,
                     int mode,
                     String workerNamePrefix) {
    this.workerNamePrefix = workerNamePrefix;
    this.factory = factory;
    this.ueh = handler;
    this.config = (parallelism & SMASK) | mode;
    long np = (long)(-parallelism); // offset ctl counts
    this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}

parallelism默认是cpu核心数,ForkJoinPool里线程数量依据于它,但不表示最大线程数,不要等同于ThreadPoolExecutor里的corePoolSize或者maximumPoolSize。

factory是线程工程,不是新东西了,默认实现是

DefaultForkJoinWorkerThreadFactory。

workerNamePrefix是其中线程名称的前缀,默认使用“ForkJoinPool-*”

config保存不变的参数,包括了parallelism和mode,供后续读取。mode可选FIFO_QUEUE和LIFO_QUEUE,默认是LIFO_QUEUE,具体用哪种,就要看业务。

ctl是ForkJoinPool中最重要的控制字段,将下面信息按16bit为一组封装在一个long中。

  • AC: 活动的worker数量;

  • TC: 总共的worker数量;

  • SS: WorkQueue状态,第一位表示active的还是inactive,其余十五位表示版本号(对付ABA);

  • ID:  这里保存了一个WorkQueue在WorkQueue[]的下标,和其他worker通过字段stackPred组成一个TreiberStack。后文讲的栈顶,指这里下标所在的WorkQueue。

TreiberStack:这个栈的pull和pop使用了CAS,所以支持并发下的无锁操作。

AC和TC初始化时取的是parallelism负数,后续代码可以直接判断正负,为负代表还没有达到目标数量。另外ctl低32位有个技巧可以直接用sp=(int)ctl取得,为负代表存在空闲worker。

线程池缺不了状态的变化,记录字段是runState,具体介绍在后面的“ForkJoinPool状态修改”。

任务ForkJoinTask

ForkJoinPool执行任务的对象是ForkJoinTask,它是一个抽象类,有两个具体实现类RecursiveAction和RecursiveTask。

public abstract class RecursiveAction extends ForkJoinTask<Void> {
    protected abstract void compute();

    public final Void getRawResult() { return null; }

    protected final void setRawResult(Void mustBeNull) { }

    protected final boolean exec() {
        compute();
        return true;
    }
}

public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
    V result;

    protected abstract V compute();

    public final V getRawResult() {
        return result;
    }

    protected final void setRawResult(V value) {
        result = value;
    }

    protected final boolean exec() {
        result = compute();
        return true;
    }
}

ForkJoinTask的抽象方法exec由RecursiveAction和RecursiveTask实现,它被定义为final,具体的执行步骤compute延迟到子类实现。很容易看出RecursiveAction和RecursiveTask的区别,前者没有result,getRawResult返回空,它们对应不需要返回结果和需要返回结果两种场景。

ForkJoinTask里很重要的字段是它的状态status,默认是0,当得出结果时变更为负数,有三种结果:

  • NORMAL

  • CANCELLED

  • EXCEPTIONAL

除此之外,在得出结果之前,任务状态能够被设置为SIGNAL,表示有线程等待这个任务的结果,执行完成后需要notify通知,具体看后文的join。

ForkJoinTask在触发执行后,并不支持其他什么特别操作,只能等待任务执行完成。CountedCompleter是ForkJoinTask的子类,它在子任务协作方面扩展了更多操作。我们聚焦ForkJoinPool主线流程,CountedCompleter相关内容另文再介绍。

WorkQueue

WorkQueue是一个双端队列,它定义在ForkJoinPool类里。

scanState描述WorkQueue当前状态:

  • 偶数表示RUNNING

  • 奇数表示SCANNING

  • 负数表示inactive

stackPred是WorkQueue组成TreiberStack时,保存前者的字段。

ForkJoinPool状态修改

  • STARTED

  • STOP

  • TERMINATED

  • SHUTDOWN

  • RSLOCK‍‍‍‍

  • RSIGNAL

runState记录了ForkJoinPool的运行状态,除了SHUTDOWN是负数,其他都是正数。前面四种不用说了,线程池标准状态流转。在多线程环境修改runState,不能简单想改就改,需要先获取锁,RSLOCK和RSIGNAL就用在这里。

private int lockRunState() {
    int rs;
    return ((((rs = runState) & RSLOCK) != 0 ||
             !U.compareAndSwapInt(this, RUNSTATE, rs, rs |= RSLOCK)) ?
            awaitRunStateLock() : rs);
}

修改前调用lockRunState锁定,检查当前状态,尝试一次使用CAS修改runState为RSLOCK。需要状态变化的机会很少,大多数时间一次就能成功,但不能排除少几率的竞争,这时候进入awaitRunStateLock。

private int awaitRunStateLock() {
    Object lock;
    boolean wasInterrupted = false;
    for (int spins = SPINS, r = 0, rs, ns;;) {
        //1
        if (((rs = runState) & RSLOCK) == 0) {
            if (U.compareAndSwapInt(this, RUNSTATE, rs, ns = rs | RSLOCK)) {
                if (wasInterrupted) {
                    try {
                        Thread.currentThread().interrupt();
                    } catch (SecurityException ignore) {
                    }
                }
                return ns;
            }
        }
        else if (r == 0)
            r = ThreadLocalRandom.nextSecondarySeed();
        else if (spins > 0) {
            r ^= r << 6; r ^= r >>> 21; r ^= r << 7; // xorshift
            if (r >= 0)
                --spins;
        }
        //2
        else if ((rs & STARTED) == 0 || (lock = stealCounter) == null)
            Thread.yield();   // initialization race
        //3
        else if (U.compareAndSwapInt(this, RUNSTATE, rs, rs | RSIGNAL)) {
            synchronized (lock) {
                if ((runState & RSIGNAL) != 0) {
                    try {
                        lock.wait();
                    } catch (InterruptedException ie) {
                        if (!(Thread.currentThread() instanceof
                              ForkJoinWorkerThread))
                            wasInterrupted = true;
                    }
                }
                else
                    lock.notifyAll();
            }
        }
    }
}

在自旋中,第一步,mark1再次尝试修改runState为RSLOCK,成功直接返回。

mark2检查ForkJoinPool初始化情况,这里没有额外多写个变量做锁,直接利用了stealCounter这个原子变量。因为初始化时(后文的externalSubmit),才会对stealCounter赋值。所以当状态不是STARTED或者stealCounter为空时,让出线程等待。

mark3处,线程不会无限制自旋尝试,会利用wait/notify进入阻塞等待。RSIGNAL代替原状态,表示有线程进入了等待,解锁时要处理。在高并发下,这不是一个好的设计,但进入这里的几率很低,作为兜底还是可以的。

private void unlockRunState(int oldRunState, int newRunState) {
    if (!U.compareAndSwapInt(this, RUNSTATE, oldRunState, newRunState)) {
        Object lock = stealCounter;
        runState = newRunState;              // clears RSIGNAL bit
        if (lock != null)
            synchronized (lock) { lock.notifyAll(); }
    }
}

解锁的逻辑就比较简单,如果顺利将状态修改为目标状态,成功大吉。否则表示有别的线程进入了wait,需要调用notifyAll唤醒,重新尝试竞争。

ForkJoinPool最佳实践

(1)最适合的是计算密集型任务

(2)在需要阻塞工作线程时,可以使用ManagedBlocker;

(3)不应该在RecursiveTask的内部使用ForkJoinPool.invoke()/invokeAll();

总结

(1)ForkJoinPool特别适合于“分而治之”算法的实现;

(2)ForkJoinPool和ThreadPoolExecutor是互补的,不是谁替代谁的关系,二者适用的场景不同;

(3)ForkJoinTask有两个核心方法——fork()和join(),有三个重要子类——RecursiveAction、RecursiveTask和CountedCompleter;

(4)ForkjoinPool内部基于“工作窃取”算法实现;

(5)每个线程有自己的工作队列,它是一个双端队列,自己从队列头存取任务,其它线程从尾部窃取任务;

(6)ForkJoinPool最适合于计算密集型任务,但也可以使用ManagedBlocker以便用于阻塞型任务;

(7)RecursiveTask内部可以少调用一次fork(),利用当前线程处理,这是一种技巧;

Garnett还会不断的分享技术干货的,希望你们是我最好的观众!

乐于输出干货的Java技术公众号:Garnett的Java之路。公众号内有大量的技术文章、海量视频资源、精美脑图,不妨来关注一下!回复【资料】领取大量学习资源和免费书籍!

Fork/Join工作原理解析 一、如何高效利用CPU 在介绍Fork/Join框架之前,思考一个问题,在多线程的情况下,如何才能高效的利用CPU,以常用的线程池为例,线程池的线程数设置多少合适? 通常调整线程池中的线程数量其核心目的就是为了能够充分的利用CPU和内存资源,从而最大限度的提高程序的性能。在实际工作中,我们可能会根据任务类型的不同而采取不同的策略。按照一个任务执行过程中使用CPU的时间和任务总耗时的比例,可以将任务分为两种:CPU密集型任务和IO密集型任务 1.1 任务类型 1.1.1 CPU密集型任务 CPU密集型任务也称 阅读详情

相关推荐

JUC(5) : ForkJoinPool | 线程的极致管理

首先,查看该类介绍,该类是 Doug Lea 在JDK1.7版本中添加的新的线程池。Fork为分叉,Join 为连接,而其设计的思想也确实是先分开处理后合并处理的工作原理。核心思想就是把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务的结果。Fork:把一个复杂任务进行分拆,大事化小Join:把分拆任务的结果进行合并复制代码这个思想就很像 Hadoop 中 MapReduce 的工作原理,都是先组装成各个 Map ,然后在 Reduce 中完成合并。

BASK2312的博客 1050

ForkJoinPool

ForkJoinPool是ExecutorService的实现类,也是一种特殊的线程池。它提供了如下两个常用的构造器: ForkJoinPool(int parallelism):创建一个包含parallelism个并行线程的ForkJoinPool。 ForkJoinPool():以Runtime.availableProcessors()方法的返回值作为parallelism参数来创建ForkJoinPool。 在java8进一步丰富了ForkJoinPool的功能,为其增加了通用池功能。通过如下两

ProMonkey_chen的博客 4258

C语言练习题:据说一个人的标准体重应该是其身高(单位:厘米)减去100、再乘以0.9所得到的公斤数。已知市斤的数值是公斤数值的两倍。现给定某人身高,请你计算其标准体重应该是多少?

题目内容: 据说一个人的标准体重应该是其身高(单位:厘米)减去100、再乘以0.9所得到的公斤数。已知市斤的数值是公斤数值的两倍。现给定某人身高,请你计算其标准体重应该是多少?(顺便也悄悄给自己算一下吧……) 输入格式: 输入第一行给出一个正整数H(100 < H ≤ 300),为某人身高。 输出格式: 在一行中输出对应的标准体重,单位为市斤,保留小数点后1位。 输入样例: 169 输出样例: 124.2 代码如下: #include<stdio.h> int main() { i

weixin_44877226的博客 2万+

C++程序员应了解的那些事(37续)C++11并发编程-条件变量(condition_variable)

<并发编程实例代码> #include <iostream> #include <chrono> #include <thread> #include <mutex> #include <condition_variable> const size_t maxThreads = 10; static int runcount = 0; static int sum = 0; std::condition_variable co.

qfturauyls的博客 525

基于R语言的DICE模型实践技术应用;预测不同减排路径下的经济增长、碳排放量、气候变化程度以及社会福利变化

DICE模型是一个动态综合气候经济模型,由诺贝尔经济学奖得主William Nordhaus开发,用于评估气候变化对经济的影响以及不同减排政策的经济成本和效益。以下是一个关于DICE模型在气候变化影响评估中的实际应用案例,结合R语言代码进行讲解。

KY_chenzhao的博客 867

电机传动机构的转矩与惯量折算

参考《工业运动控制》

庐州牧 1万+

2024金三银四巨作!10万字!阿里高工纯手码JDK源码笔记 带你飙向实战

一般像这样的大企业都有好几轮面试,所以自己一定要花点时间去收集整理一下公司的背景,公司的企业文化,俗话说「知己知彼百战不殆」,不要盲目的去面试,还有很多人关心怎么去跟HR谈薪资。这边给大家一个建议,如果你的理想薪资是30K,你完全可以跟HR谈33~35K,而不是一下子就把自己的底牌暴露了出来,不过肯定不能说的这么直接,比如原来你的公司是25K,你可以跟HR讲原来的薪资是多少,你们这边能给到我的是多少?你说我这边希望可以有一个20%涨薪。

2401_84048671的博客 971

SRCNN超分辨率Pytorch实现,代码逐行讲解,附源码

超分辨率,就是把低分辨率(LR, Low Resolution)图片放大为高分辨率(HR, High Resolution)的过程。通过CNN将图像Y 的特征提取出来存到向量中。用一层的CNN以及ReLU去将图像Y 变成一堆堆向量,即feature map。把提取到的特征进一步做非线性映射,加大网络深度,提高网络复杂性。结合了前面得到的补丁来产生最终的高分辨率图像。实验步骤。

weixin_52261094的博客 3万+

JAVA并发多线程面试真题 2020最新最全丨来自阿里、百度、美团、京东多家大厂

写在前面 随着Java程序员的大幅增长和需求方对 Java 程序员提出更加严苛的选材标准,Java 程序员正面临着更加激烈的竞争。而在这其中,Java并发编程是一个优秀的开发者成长过程中绕不过去的挑战 在开发、测试以及调试多线程程序时存在着巨大的困难,因为并发性错误通常并不会以某种确定的方式显现出来。当这些错误出现时,通常是在最糟糕的时刻,例如在正式产品中,或者在高负载的情况下,当并发性错误被发现时,情况总是比较紧急甚至可以打倒整个项目团队,毁掉整个项目。 因此,Java并发与多线程就成了Ja..

weixin_48673251的博客 261

电子产品的老化测试有哪些类型?

因此,越来越多的人在寻找“已知合格的测试连接器”,即经过测试具有可靠性和有效性的测试连接器,可以在老化测试中制定优化方法。例如对于芯片来说,不同的封装有不同的老化测试座,QFP老化测试座,QFN老化测试座等。老化测试时,专用老化电路板上的元件将承受等于或高于其额定工作条件的压力,以消除任何在额定寿命之前过早失效的元件。静态老化:在此类系统中,将测试设备安装到老化板上的插座中,然后放入老化箱中,其中每隔12至24小时施加电源和高温。动态老化:在动态老化系统中,设备以老化速度设定的最大速率进行测试。

软件技术的专栏 1839

YOLO26训练教程,训练自己的数据集教学,从源码下载,配置虚拟环境,准备数据集、训练、验证、推理测试 。实现0到1的完整教学过程。快速入门必看

本文详细介绍了YOLO26目标检测模型的配置与使用流程:1)下载YOLOv11源码并创建Python3.10虚拟环境;2)安装PyTorch2.5.1及相关依赖库;3)准备数据集并修改配置文件;4)编写训练脚本设置参数(640x640分辨率、500轮次);5)验证模型效果;6)使用训练好的模型进行推理测试;7)生成可视化热力图。整个过程涵盖了从环境搭建到模型应用的全流程,并提供了清华镜像源加速下载。

Ai缝合怪、助力小伙伴高效发论文 5283

2024最新版!阿里P8大佬纯手打“JDK源码笔记” 带你飙向实战

千千万万要记得:多刷题!!多刷题!!之前算法是我的硬伤,后面硬啃了好长一段时间才补回来,算法才是程序员的灵魂!!!!篇幅有限,以下只能截图分享部分的资源!!(1)多线程(这里以多线程为代表,其实整理了一本JAVA核心架构笔记集)(2)刷的算法题(还有左神的算法笔记)(3)面经+真题解析+对应的相关笔记(很全面)(4)视频学习(部分)ps:当你觉得学不进或者累了的时候,视频是个不错的选择在这里,最后只一句话:祝大家offer拿到手软!!本文已被。

2401_84689725的博客 388

个人量化系统的策略集合与赚钱之道:全栈深度解析 + 与主流开源框架对比

前三篇把系统的"骨架"拆完了——架构、工程化、部署。本篇回答最关键的问题:一句话:别人的系统是"程序员的系统"(写代码才能加策略),daily_stock_analysis 是"股民的 YAML"(写中文就能加策略)。这个差异决定了它能在多大程度上真的帮你赚钱。如果你用过 、、,加一个策略的流程是: 问题在哪?在 daily_stock_analysis 里,“策略” = 一个文件。每个 YAML 是一个自然语言描述的交易策略,包括: 看到没? ——整个策略就是一段中文,零代码。任何会写字的人都能加新策略

shiter编写程序的艺术 153

2024金三银四巨作!10万字!阿里高工纯手码JDK源码笔记 带你飙向实战(1)

三个工作日收到了offer,头条面试体验还是很棒的,这次的头条面试好像每面技术都问了我算法,然后就是中间件、MySQL、Redis、Kafka、网络等等。关于算法,我觉得最好的是刷题,作死的刷的,多做多练习,加上自己的理解,还是比较容易拿下的。而且,基本上都是面试真题解析、笔记和学习大纲图,感觉复习也就需要这些吧(个人意见)废话,当然有难度啦,不然我也不会到现在都还没看完,而且看了也经常忘,哭唧唧…毕竟像JDK这种源码,和我们平常练手写小例子、写业务代码不一样,人家毕竟是,为了等因素考虑,加入了很多。

2401_84692141的博客 888

10.28华为OD机试真题 新系统 - 社交网络消息转发 (Java/Py/C/C++/Js/Go)

10.28华为OD机试真题 新系统 - 社交网络消息转发 (Java/Py/C/C++/Js/Go)

算法大师! 417

怀旧版电脑GBC模拟器

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

13万字!腾讯高工手写JDK源码笔记 带你飙向实战

由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档还有更多面试复习笔记分享如下由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档[外链图片转存中…(img-y1hEZHD2-1710391437359)]还有更多面试复习笔记分享如下[外链图片转存中…(img-6c8aSyv7-1710391437360)]本文已被。

m0_60388338的博客 367

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

只要源码学的好,offer 不会少,阿里 JDK 源码学习指南(终极版)看看别人怎么学源码(1)

如果你选择了IT行业并坚定的走下去,这个方向肯定是没有一丝问题的,这是个高薪行业,但是高薪是凭自己的努力学习获取来的,这次我把P8大佬用过的一些学习笔记(pdf)都整理在本文中了《Java中高级核心知识全面解析》小米商场项目实战,别再担心面试没有实战项目:本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录需要这份系统化的资料的朋友,可以点击这里获取orums/4f45ff00ff254613a03fab5e56a57acb)收录**

2401_84557849的博客 334

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

深入剖析线程池基本原理以及常见面试题详解

文章目录面试官:能给我讲讲线程池的实现原理?线程池类继承关系ThreadPoolExecutor核心数据结构面试官:给我讲讲线程池的有哪些参数?面试官:如何优雅的关闭线程?线程的生命周期面试官:线程池哪五种状态?面试官:线程池哪4种拒绝策略?并分别说一下作用和实现原理?DiscardOldestPolicyAbortPolicyDiscardPolicyCallerRunsPolicy面试官:线程池常用的阻塞队列有哪些?能说下各自的区别?SynchronousQueue应用PriorityBlockedQu

dreamzuora的博客 871

胜天半子!阿里技术官纯手打并发编程神笔记限时开源,手慢无

大型分布式系统犹如一个生命,系统中各个服务犹如骨骼,其中的数据犹如血液,而Kafka犹如经络,串联整个系统。这份Kafka源码笔记通过大量的设计图展示、代码分析、示例分享,把Kafka的实现脉络展示在读者面前,帮助读者更好地研读Kafka代码。麻烦帮忙转发一下这篇文章+关注我。这份Kafka源码笔记通过大量的设计图展示、代码分析、示例分享,把Kafka的实现脉络展示在读者面前,帮助读者更好地研读Kafka代码。麻烦帮忙转发一下这篇文章+关注我。

jixuczy的博客 946

阿里巴巴JDK源码学习笔记:程序员进阶必备!

作为Java开发者,面试肯定被问过多线程。对于它,大多数好兄弟面试前都是看看八股文背背面试题以为就OK了;殊不知现在的面试官都是针对一个点往深了问,你要是不懂其中原理,面试就挂了。可能你知道什么是进程什么是线程,但面试官要是问你进程之间是如何通讯的?ConcurrentHashMap 和 HashTable有什么区别?为什么wait和notify方法要在同步块代码中调用?你答不上来就只能等通知了。。。

weixin_50205273的博客 272

只要源码学的好,offer 不会少,阿里 JDK 源码学习指南(终极版)看看别人怎么学源码

又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考以下是部分内容截图本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录需要这份系统化的资料的朋友,可以点击这里获取/4f45ff00ff254613a03fab5e56a57acb)收录**需要这份系统化的资料的朋友,可以点击这里获取。

2401_84557849的博客 322

Java线程池核心原理与高频面试题解析

线程池作为并发编程的核心组件,通过线程复用和任务队列机制,有效解决了直接创建线程的系统开销和资源管理问题。其底层基于ThreadPoolExecutor实现,通过corePoolSize、workQueue等关键参数实现资源控制。在Java生态中,线程池广泛应用于高并发场景如电商秒杀、异步日志等,配合拒绝策略和状态管理保障系统稳定性。本文深入解析线程池执行流程、四种拒绝策略对比等高频面试考点,结合JMX动态调整、线程池隔离等实战经验,并探讨虚拟线程(Loom)等新技术演进。掌握线程池原理对Java开发者至关

weixin_33462167的博客 300

为了对抗内卷,我“偷”了阿里两份笔记:JDK 源码 +Java 并发图册

Java 并发在近几年的面试里面可以说是面试热点,每个面试官面试的时候都会跟你扯一下并发,甚至是高并发。面试前你不仅得需要弄清楚的是什么是并发,还得搞清什么是高并发! 在这里很多小白朋友就会很疑惑:我工作又不用,为啥面试总是问?真就内卷卷我呗!(手动狗头)互联网内卷已经是现在的行业趋势,而且是不可逆的,这个大家也知道;但 LZ 要说的是,虽然简单地增删改查并不需要并发的知识,但是业务稍微复杂一点,你的技术水平稍微提升一点的话你就会知道,并发是我们 Java 程序员绕不开的一道坎。 如何打开并发世界的大门

Java_may的博客 240
上一篇: 百万级高并发系统,带你近距离体验上线过程
下一篇: 大厂都用DevOps,从多角度了解DevOps
狼王编程
博客等级 码龄8年 1270粉丝 318原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值