ThreadPoolExecutor线程池 —————— 开开开山怪

Python ThreadPoolExecutor并发小结 最近项目中由于需要频繁调用高德地图的接口,发现采用单线程循环调用太慢了,于是上网查了一下Python的并发,于是决定采用ThreadPoolExecutor(线程池)做并发。 在此之前,先看一下,单线程循环爬取多个网页的耗时: import time import requests import threading def get_html(url): print('thread id:',threading.currentThread().getName(),' 访问了:',url) 阅读详情

ThreadPoolExecutor线程池

在这里插入图片描述
说起线程池大家一定都不陌生,其实在很多地方都有应用。
当我们解释为什么使用线程池的时候,我们都会说线程池可以减少线程创建的开销,节省系统的资源。
但其实线程池真正做到我们说的那样,还和我们设置的参数有关。我们都知道在我们使用线程池的时候,开头要搞很多参数。

 public ThreadPoolExecutor
 (int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue){
 }                                                             

这些个参数,相信你们一定不会陌生,在使用的时候还要考虑半天,这参数我该怎么设置好呢?

那我们首先来了解这些个参数的意义,这些参数大多对应的也是线程池中的成员,既然线程池的源码中对于往线程池中添加一个任务采用的是这样 addWorker , Worker, workerCountOf.

看到Woker这样的字眼,我觉得采用工人这个词来可能会更加的清晰。

先说一个误区:在我们new ThreadPoolExecutor()的时候设置核心线程数,不是说我们new 完之后这些个线程就已经存在,而是之后我们向线程池采用execute添加任务(Runnable)时才会创建线程,核心线程的数量只能说明是一个数量上的限制,当超过这个数量就是另当别论了。

其实采用execute添加Runnable任务的时候,是将任务Runnable交给一个Worker,然后启动该worker,进行任务的执行。
所以线程池中的线程,我下面都采用工人(Worker)这个字眼来代替.

1. private volatile int corePoolSize;

例如 corePoolSize = 5
线程池始终保持有5个工人,不管这些个工人是在核心工人数没有到达5这个阀值时创建的工人,还是由于已经有5个工人在执行任务,已经创建的5人都在忙的状态,而此时阻塞队列也满了,线程池会创建新的工人(也即后创建的工人)。

不管是先创建的还是后创建的,不会说等所有工人都没有任务执行的时候保留先创建的5个,而解除后创建的工人这一说。而是始终保持所有创建的工人中的5个。

2. private volatile int maximumPoolSize;
线程池能容纳的最多的工人数量,

3. private volatile long keepAliveTime;
当工人的数量大于核心工人的数量的时候,不管是哪个工人执行完自己手头的任务,去阻塞队列进行取任务的时候,都会等待这个时间,如果阻塞队列中有任务,那就万事大吉,这个工人还能用,如果阻塞队列中迟迟没有任务,那么该工人就采用这个时间等等,时间过了,没有任务,那么该工人就该下岗了。

4. TimeUnit unit 这个不是成员,
例如unit = TimeUnit.MILLISECONDS,是采用毫秒进行计算
就是由用户来设置,对于 keepAliveTime采用怎样的单位进行计算,计算出最终的等待时间,

5. private final BlockingQueue workQueue;
用于存储任务的阻塞队列,当核心数量的工人都在执行任务的时候,再来新的任务,就会添加到这个阻塞队列中。

正儿八经线程池工作原理

情况1 : 当工人的数量没有超过核心工人数量的这个阀值的时候

来一个任务新建一个工人,等待工人去执行,那么此时来说,阻塞队列肯定为空,如果此时没有任何的新任务进来,那么工人执行完自己的任务也不闲着,会去阻塞队列中去看一眼,看有没有任务等待执行,那么此时肯定是没有的,所以当前执行完自己任务的工人就会阻塞起来。

情况2 : 当工人数量达到核心工人数量的时候,但阻塞队列没有满的时候

此时是不会针对新来任务创建工人的,而是将任务加到阻塞队列中,所以针对 情况1 的工人阻塞就可以处理了,因为 情况2 是不会创建新的工人,只是将任务加入到阻塞队列,所以如果在 情况1 由于工人没有任务可执行,工人阻塞起来,那么在此时,由于阻塞队列有任务,就会唤醒那些阻塞起来的工人,阻塞队列中加一个任务,唤醒一个工人。那么工人就有任务可执行了,(这也就是我们为什么说线程池节省系统资源的原因,因为工人没闲着,只要有活就干,相当于一个线程执行完一个任务,线程不会消亡而是在任务队里中再拿任务执行,可以做到线程的循环使用)。

情况3:当工人达到核心工人数量的时候,阻塞队列也已满,而且核心数量的这哥几个工人还都手头有任务没有执行完。

此时如果有任务来,线程池就不得不创建工人了,没办法,任务都堆积如山了,那么此时新创建的该工人就执行当前任务。
如果此时由任务来,会出现两种情况:
3.1 :该新建工人还在执行任务,之前的工人也还都在执行任务,阻塞队列还是满的,那么线程池就再创建一个工人执行新任务,如果一直是我说的这种情况,(不管是之前的工人还是现在的工人都在执行任务,并且阻塞队列里的任务还都是满的话),那么对于新任务就创建一个工人,直到工人的数量大于线程池容纳最多工人的数量,此时线程池就不接任务了,同时也会执行拒绝策略 (但是一般这种情况很少出现)。

3.2:新创建的工人或者之前的工人有一个或多个已经将手头的任务执行完了,那么此时阻塞队列就会有空位,因为那些执行完任务的工人会去阻塞队列去拿任务执行,那么此时来的新任务就进入阻塞队列,而不是重新创建工人执行了。

对于上边的 情况3 来说,不管是情况3 下两种情况的哪一种情况,都说明已经建立了新的工人(比核心数量多的工人),顾名思议就是,此时的工人的数量已经超过核心工人数量。

对于 情况3 的第一种情况来说属于极端情况,那当然极端情况就是线程池不再接受新的任务了,就算不接受新的任务,那么线程池也会面临下面的问题。
迟早工人们(工人数量大于核心工人的数量)会完成自己的手头工作,去阻塞队列中拿任务执行,阻塞队列任务也会被拿完的情况(这种情况当然是我们的正常情况,哪有说工人执行任务的速度还赶不上添加任务的速度,那这效率也太慢了).

此时分为两种情况,只要有工人完成手中的任务,去阻塞队列中拿任务,拿到任务执行就完事了,随着所有工人渐渐的完成自己手中任务都挨个去阻塞队列中拿任务的时候,总有人会因为阻塞队列为空而拿不到任务,此时我们说的那个线程池中的变量keepAliveTime派上用场了,如果某个工人等了keepAliveTime时间,阻塞队列还为空,线程池就会让该工人下岗了,但对于这个下岗来说,有一个限制就是corePoolSize,所以这还得看那些工人的命,要是某一个工人去阻塞队列中拿任务,没有拿到,并且此时总的工人数量大于和核心工人数量,就让该工人下岗,如果小于核心工人的数量,那算这个工人命好没有被迫下岗(这也就是我前面说的,corepollSize的作用就是用来维持工人的数量的,没有什么核心工人这一说,也就是核心线程。只是说线程池要留够corepollSize这个数量的工人,至于留谁那看工人的命了,也就是刚才说过的。).
那么现在的局面也就回到了我们 情况2 的局面。

这就是整个线程池的工作原理,也是一个不断轮回的过程,直到线程池关闭,线程池关闭也讲究善后工作,那就是对这些工作的工人和任务的处理了。善后工作这篇文章不做说明,可能会在之后的博文中进行讲解,拒绝策略方面的内容。

对于我上面说的那几种情况,你们可以对照源码多走几遍,就意思自己采用手工过程,验证一下上边的情况。
对于线程池工作的原理,上边就是我看源码总结出来的,下面我们跟着源码再大致看看。

源码解析;

入口方法execute(Runnable)

 public void execute(Runnable command) {
        if (command == null)//这判断不用多说,你们肯定都晓得,
            throw new NullPointerException();
       
        int c = ctl.get();
//这个ctl是 AtomicInteger这个类的一个对象,这个类中有一个int value的成员,用这个AtomicInteger其实想用value,
//ctl.get()返回的就是value值,同时这个值在我们new ThreadPoolExecutor的时候默认值为-1 << 29
//采用这个值,一是想采用value的前三位表示线程池的状态,后29位表示线程池中工人的个数。
        if (workerCountOf(c) < corePoolSize) {
 //workerCountOf(c)就是去除value就是取出value的后29 判断工人数量。
 //当前的工人的数量还没有到核心工人数量这个阀值的时候
 //采用addWorker创建一个工人,来执行任务。 
 //下面会说到这个addworker方法
            if (addWorker(command, true))//
                return;
            c = ctl.get();
        }
 //当目前工人的数量大于核心工人数量的时候,直接到这个判断
 // isRunning(c)通过value判断线程池的状态,是否在运行状态,在运行转状态才有后话
 //线程池处于运行状态,将新任务直接加入到阻塞队列中      
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
 //这里会出现一个情况就是,当加入新任务成功之后,此时线程池的状态不再为运行状态,那就意味值线程池要关闭。
 //那就试图将新添加的这个任务从阻塞队列中移除,如果删除成功,就执行拒绝策略(线程池关闭的善后工作)
 //如果删除失败,也没关系,反正线程池已经不工作,对于阻塞队列中的任务也会进行善后处理的。
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
 //当线程池中工人的数量大于核心工人数量,并且,阻塞队列也满的情况下
 //创建新的工人执行任务,addWorker(command, false)返回true
 //如果当前总的工人数量大于线程池最大工人的数量,那么此时创建工人就会失败,addWorker(command, false)返回false,则执行拒绝策略。       
         else if (!addWorker(command, false))
            reject(command);
  }

addworker()

private boolean addWorker(Runnable firstTask, boolean core) {
   retry:
   //这个外层for循环就是用来判断当前是否满足创建工人的条件
   for (;;) {
       int c = ctl.get();
       int rs = runStateOf(c);
       //rs为当前线程池的状态
       //如果当前线程池的状态为关闭状态,并且阻塞队列为空的话,就不满足创建一个新工人的条件,
       //显然只要线程池的状态为Running状态,也就是运行态,当然不会进入到这个判断,也就离创建工人的可能性更进了一步
       if (rs >= SHUTDOWN && ! (rs == SHUTDOWN &&
                     firstTask == null &&
                     ! workQueue.isEmpty()))
              return false;
          
       for (;;) {
       //判断当前线程池中的工人的数量
          int wc = workerCountOf(c);
       //对于下面这个判断是针对不同的情况,当大于核心工人数量,返回false,表明当前添加的任务不再创建工人执行,而是加入阻塞队列汇中
      //当大于最大工人的数量的时候,返回false,那时候说明要执行拒绝策略 
          if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
              return false;
       //程序到这里表明可以创建工人的条件满足,将ctl中的value值加一,
          if (compareAndIncrementWorkerCount(c))
                    break retry;//直接跳出外层的for
                   
          c = ctl.get();  
          if (runStateOf(c) != rs)
              continue retry;
   }//外层for循环结束
   
  //程序到这里表明创新工人的条件成立,可以创建工人了
   boolean workerStarted = false;                
   boolean workerAdded = false;
   Worker w = null;                       
   try {
   //将任务交给工人,并且创建一个工人,Worker类继承了Runnable
   //将任务交个工人,那么会将firstTask赋值为Worker中的 Runnable firstTask成员。
   //由于worker本身是一个Runnable所以,在创建工人的时候,工人将自己本身赋值给Worker中的Thread thread成员,
   //worker中的run方法调用了runWorker(this),               
       w = new Worker(firstTask);
       final Thread t = w.thread;
  //w.thread,就是工人本身。在之后你们看到的他 t.start
  //就说明工人等待机会执行任务。那么t.start 会有一个线程的开启,并且之后会执行t中的run方法,因为上边说了t就是worker对象本身
  //所以会执行runworker(this)方法,表明工人开始执行任务了,而this就是worker对象
  //而在runworker(this)方法中就执行了this中的firsttask的run方法,也就是该woker对象所拥有的任务firstTask的run方法,
  //也就是真正执行了任务firstTask。
       if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
  //这个锁不用说了,因为线程中有一个变量, HashSet<Worker> workers  
  //如果工人创建成功的话,需要将工人加入到这个workers中,为了多线程安全的问题,采用mainLock.lock(); 保证在添加工人和删除工人的同步。  
            mainLock.lock();
            try {            
                int rs = runStateOf(ctl.get());
              //获取当前的线程池的状态,
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)){
                    if (t.isAlive())
                        throw new IllegalThreadStateException();
               //将工人添加到workers中               
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                //largestPoolSize默认为零,添加工人的时候,对其值进行更改记录。
                   
                        largestPoolSize = s;
                    workerAdded = true;
                 }
            } finally {
            //释放锁的过程,让其他想要添加或者删除工人的线程可以持有锁进行添加或者删除。
                mainLock.unlock();
            }
         //当工人添加好之后
         //t.start就是创建一个线程,在上面 new woker的时候已经说过了
         //等待线程运行的时候,会执行woker中的firstTask的run方法。相当于真正执行任务
            if (workerAdded) {
                t.start();
            //不是立马执行任务,先创键线程,等待cpu调度线程执行任务,我也比作是工人执行任务。
                workerStarted = true;
            }
         }
     } finally {
       if (! workerStarted)
         addWorkerFailed(w);
     }
     return workerStarted;
 }
                        

runWoker()
runwoker在前面创建工人的时候,已经提到过了。当创建完工人之后有一个t.start 的过程,那么这个 t 就是Woker对象,t.start在之后创建的线程会在之后运行会调用 t 中run方法,也就是Woker中的run方法,也就会运行runwoker()并且参数就是Woker对象

private final class Worker extends AbstractQueuedSynchronizer  implements Runnable{
        public void run() {
            runWorker(this);
        }
}
        
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    //w.firstTask就是woker中的任务,也就是工人携带的任务,
    //当一个线程运行runworker的时候,就会执行工人携带的任务,也相当于工人执行了任务。
    Runnable task = w.firstTask;
    //先将工人携带的任务设置为null,然后执行任务
    w.firstTask = null;
    w.unlock();
    boolean completedAbruptly = true;
    try {
    //当前线程执行完一个task,会在循环采用getTask在阻塞队列中拿任务执行,
    //也相当于一个工人执行完任务,再拿任务执行。
       while (task != null || (task = getTask()) != null) {
           w.lock();
           if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() && 
                 runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
               wt.interrupt();
           try {
           //在线程中此方法并没有具体的实现,空方法,不必管
               beforeExecute(wt, task);
               Throwable thrown = null;
               try {
               //执行task.run方法,也是真正执行任务了
                  task.run();
               } catch (RuntimeException x) {
                  thrown = x; throw x;
               } catch (Error x) {
                  thrown = x; throw x;
               } catch (Throwable x) {
                  thrown = x; throw new Error(x);
               } finally {
               //线程池并没有实现,空方法
                  afterExecute(task, thrown);
               }
            } finally {
               task = null;
              //当前线程执行完一个任务,也相当于一个工人执行完任务
              //工人记录自己完成的任务数量。
               w.completedTasks++;
               w.unlock();
            }
        }
        completedAbruptly = false;
     } finally {
     //当上边的while循环结束,程序就会运行到这里
     //让工人下岗的操作,相当于将工人在workers中删除
         processWorkerExit(w, completedAbruptly);
      //while循环结束相当于是,在取任务的时候没取到,返回了null
      //上边的while循环才会结束
      //下面我们就来看看getTask方法。   
     }
 }

getTask();

 private Runnable getTask() {
      boolean timedOut = false;
      for (;;) {
           int c = ctl.get();
           int rs = runStateOf(c);
          //获取线程池的状态   
           if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
          //当线程池为关闭状态的时候 先通过decrementWorkerCount();将工人数量减一
          //然后返回null,那么runwoker中的while循环就会结束,并且删除工人
                decrementWorkerCount();
                return null;
            }
         // 获取线程池中工人的数量。  
            int wc = workerCountOf(c);
         //allowCoreThreadTimeOut默认是false,表示不允许核心数量的工人等待超时而进行下岗。
         //所以当工人的数量小于核心工人的数量的时候,timed = false
         //当工人的数量大于核心工人的数量的时候,timed = true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
         //在这里就是决定工人是否下岗的条件
         //当此时工人总数量大于核心工人的数量的时候,并且由于工人在阻塞队列中没有取到任何的任务,
         //那么此时满足这个if的判断,就会将工人的数量减一,并且返回null
         //那么在runwoker的while循环就会结束,就会让工人下岗
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
      //当timed为true的时候,表明此时的工人数量大于核心工人的数量,
      //此时对于任何工人来说采用poll,原因是如果当阻塞队列中没有任务的时候,这些取任务的工人还有等待的时机
     //当等待时间到了,还没有获取到任务的话,将返回null,这样timedOut = true;那么在次循环的时候,就会将获取任务的工人删除,
     //因为当工人数量大于核心工人数量,并且阻塞队列中没有任务,线程池没有必要维持那么多工人,维持核心数量个数的工人就好。
     //当timed为false的时候表明此时的工人数量小于核心工人的数量,
    //采用take的原因是,如果此时由工人去阻塞队列中拿任务,但是阻塞对列中没有任务的时候,会将当前线程阻塞,
    //也就是是说工人只是暂停工作,当阻塞队列中有任务加入的话,当前线程接着运行从阻塞队列中拿任务执行
    //因为线程池要维持核心数量的工人数,此时工人的数量小于核心工人数量,所以不会像poll一样返回null,然后剔除这个工人,只是将其暂停。
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            }catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

VLOOKUP_fixed.py 立即下载

相关推荐

D打卡签到,蓝牙中继,广播信号模拟器

模拟WIFI或蓝牙,有如下4种运行模式 1、WIFI模式:指定SSID、BSSID名称等,模拟WIFI,供设备连接。 2、蓝牙静态模式:指定MAC地址,广播固定的RAW数据。 3、蓝牙动态主机模式:扫描周围蓝牙设备,获取指定MAC地址的蓝牙及其RAW数据,实时发送至Cloud云端,可以设定时间段,只在设定的时间段内发送。 4、蓝牙动态从机模式:指定MAC地址,从Cloud云端获取动态主机模式实时上传的该MAC地址的RAW数据,并广播RAW数据。 准备工作: 1、板子:选择ESP32C3或ESP32C3 Supermini 2、nRF Connect手机app:用于扫描获取待模拟蓝牙设备的MAC和RAW数据。 Wifi分析仪或WiFi魔盒手机APP:用于扫描获取待模拟WIFI设备的SSID和BSSID。 3、可使用官方flash_download_tool_3.9.11工具来固件烧录。 使用说明: 上述完成后,设备首次上电,RESET一侧的指示灯会常亮,可以把它理解成电源指示灯; BOOT一侧的灯会亮,该灯常亮,表示设备开启了AP模式,此时设备会发射一个WIFI信号源,名称为ESPWIFI_AP,密码为12345678,手机或电脑连接该WIFI,浏览器输入192.168.4.1,进入设备的配置页面,进行相应的配置,其中动态主机模式和动态从机模式的SSID、Pwd 及要模拟的蓝牙MAC地址。配置完成并保存后,会重启,重启后设备会按照保存的配置运行。若下次要修改设备配置,可以长按BOOT按键,等待BOOT一侧的灯常亮,即进入AP模式,再按照上述流程配置即可。 仅用于蓝牙设备的开发与调试,请勿用于签到等违法违纪用途。

ThreadPoolExecutor线程池使用

ThreadPoolExecutor线程池使用简介ThreadPoolExecutor线程添加策略提交任务executesubmitinvokeAnyinvokeAllshutdownshutdownNowisShutdownisTerminatedawaitTermination 简介 线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。

World哥 392

YOLO26算法心电图十二导联目标检测+训练好的模型+368张数据集+pyqt可视化界面.zip

附训练好的 YOLO 权重与 PyQt 可视化检测界面,页面底部含可视化效果预览,毕设/课设开箱即用。 【数据集概况】 · 检测类别(中文):[导联I(I), 导联II(II), 导联III(III), 胸导联V1(V1), 胸导联V2(V2), 胸导联V3(V3), 胸导联V4(V4), 胸导联V5(V5), 胸导联V6(V6), 加压肢体导联aVF(aVF), 加压肢体导联aVL(aVL), 加压肢体导联aVR(aVR)] · 训练集:313 张 · 验证集:37 张 · 测试集:18 张 · 总计:368 张 该数据集聚焦于临床心电图信号的精准识别与定位,其核心检测目标为标准12导联心电图中的各个独立导联波形区域,涵盖肢体导联(I、II、III)与加压肢体导联(aVR、aVL、aVF),以及胸前导联(V1至V6),适用于心电图自动分析系统中对各导联位置的快速准确标注与分类。数据采集场景真实还原了医院心电图室或急诊监护环境下的标准心电图记录纸,波形清晰、基线稳定,具有高度临床参考价值。... 【训练曲线与评估图】 【模型训练配置】 参数 | 值 模型 | yolo26n 训练轮数 | 100 epochs 输入尺寸 | 640x640 批次大小 | 24 优化器 | auto 初始学习率 | 0.01 训练设备 【关键指标汇总】 训练了 57 个 epoch,最终轮指标: 指标 | 数值 mAP50 | **0.9708** mAP50-95 | 0.6344 Precision | 0.8990 Recall | 0.9191 train/box_loss | 1.2231 train/cls_loss | 0.9703 val/box_loss | 1.2779 val/cls_loss | 0.... 使用场景:毕业设计、课程设计、深度学习入门、工业场景落地验证。

多线程ThreadPoolExecutor的使用方式示例

多线程ThreadPoolExecutor的使用方式示例,自己在实际开发过程中的案例分享; private ExecutorService threadPool; @PostConstruct public void init(){ ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() ...

liaonanfeng88的优快云博客 945

企业级AI助手平台 开箱即用的全栈AI平台,支持多智能体协同、Supervisor模式编排、多种决策模式、RAG技术和流程编排能力 支持多厂商大模型统一接入与管理,安全可控的企业知识库与高精度检索优化

企业级AI应用开发框架,支持多厂商大模型统一接入与管理,安全可控的企业知识库与高精度检索优化能力,提供可视化流程编排、自主决策智能体与多智能体协同调度,兼容主流 Agent Skill 协议,帮助企业与开发者零门槛快速构建安全、高效、可落地的AI智能体应用与行业解决方案。

Java低代码, 轻量级, 支持国产化 国密 AI大模型MCP服务 商业免费 包括核心模块如:组织机构、角色用户、权限授权、数据权限、内容管理、工作流、Spring Cloud 微服务、AI大模型等

Java低代码, 轻量级, 支持国产化 国密 AI大模型MCP服务 商业免费 Spring Boot, MyBatis, Flowable, TypeScript, Vue, Antdv, 包括核心模块如:组织机构、角色用户、权限授权、数据权限、内容管理、工作流、Spring Cloud 微服务、AI大模型、代码生成器等

Java开发八股准备V1.1

反射是一种在程序运行阶段,动态获取类和执行方法的能力,该方法突破了Java编译时确定类型的限制,为动态编程提供了底层支持,很多主流框架。

写代码的小阿帆的博客 217

文创媒体电商行业智算中心(AIDC)建设规划方案.pptx

文创媒体电商行业智算中心(AIDC)建设规划方案.pptx

本地缓存-loadingCache

本地缓存 get方法: V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException { Preconditions.checkNotNull(key); Preconditions.checkNotNull(loader); Object var15; try { //c

String_guai 1万+

集团企业数字化转型AI智能体平台规划及场景应用方案.pptx

集团企业数字化转型AI智能体平台规划及场景应用方案.pptx

基于动态窗口法DWA的机器人路径规划研究(Matlab代码实现)

内容概要:本文系统研究了基于动态窗口法(DWA)的机器人局部路径规划算法,深入剖析其在动态环境中实现自主避障与实时轨迹优化的核心机制。文章详细阐述了DWA算法的速度空间采样策略、轨迹预测模型、动力学约束处理及多目标代价函数的设计原理,并通过构建完整的Matlab仿真框架验证算法有效性。结合典型场景的仿真实验,展示了机器人在复杂障碍物环境下如何实时生成安全、平滑且符合运动学约束的最优路径,充分体现了DWA在移动机器人导航中的实用性与鲁棒性。; 适合人群:具备机器人运动规划基础知识和Matlab编程能力,从事智能移动机器人、自动驾驶或服务机器人研发的研究生、科研人员及工程技术人员。; 使用场景及目标:①深入理解DWA算法的理论基础与实现细节;②掌握机器人局部路径规划的仿真实现方法;③为实际机器人系统的导航模块开发提供可复用的算法原型与代码参考;④作为进一步研究高级导航算法(如TEB、MPC)的技术基础。; 阅读建议:建议结合提供的Matlab代码逐模块分析与调试,重点关注速度采样、轨迹积分、障碍物距离计算及代价评估等关键环节,通过调整参数在不同地图环境中进行对比实验,以深刻把握算法性能与参数之间的关系,进而提升对实时路径规划系统设计的理解与应用能力。

ArrayBlockingQueue 实现多线程安全 —————— 开开开山怪

我们都知道在使用线程池的时候我们需要选择相应的阻塞队列,当核心线程池的线程处于忙的状态,那需要将新的任务加入到阻塞队列中。 一.ArrayBlockingQueue 二.LinkedBlockingQueue ...

String_guai 5345

【轴承故障诊断】用于轴承故障诊断的集中时频分析研究(Matlab代码实现)

内容概要:本文系统研究了用于轴承故障诊断的集中时频分析技术,重点解决了传统时频分析方法在处理非平稳信号时存在的能量分散与分辨率不足等问题。通过引入集中时频分析方法,能够有效聚焦信号能量,精确提取轴承故障特征频率,提升故障识别的准确性与时效性。文章详细阐述了该方法的理论基础,并结合仿真信号与实际实验数据验证其优越性能,同时提供了完整的Matlab代码实现流程,涵盖信号预处理、时频变换、参数优化及结果可视化等环节,便于读者复现与拓展应用。; 适合人群:具备一定信号处理理论基础和Matlab编程能力的研究生、科研人员,以及从事机械设备状态监测、故障诊断与预测性维护工作的工程技术人员。; 使用场景及目标:①应用于电机、风机、齿轮箱等旋转机械的早期轴承故障检测,实现故障预警;②提升工业设备运维系统的智能化水平,支撑预测性维护体系构建;③作为高校或研究机构在信号处理、机械故障诊断等课程中的教学案例与实践素材。; 阅读建议:建议读者结合文中提供的Matlab代码逐段运行与调试,深入理解集中时频分布的生成机制及其关键参数(如核函数选择、平滑窗口大小)对诊断效果的影响,并尝试将其应用于其他类型的机械故障信号分析中,开展对比研究以深化对该方法适用性的认识。

LinkedBlockingQueue多线程安全的保障 —————— 开开开山怪

LinkedBlockingQueue多线程安全的保障

String_guai 4370

第二章-Ethernet/IP基本概念

在I/O连接中,一旦连接建立,就不会有请求-响应数据,只会按照连接建立时指定的生产触发器所确定的时间间隔生产和消耗带有ConnectionID的数据。连接是通过连接管理器对象的Forward请求服务建立的,ForwardOpen请求包含所有连接参数,包括传输类、生产触发器、定时信息、电子密钥和连接ID。I/O适配器:I/O适配器从I/O扫描器接收隐式通信连接请求,然后以请求的速率生成I/O数据。I/O扫描器:启动与I/O适配器的隐式通信,需要处理连接的配置以及如何配置适配器。扫描器还支持启动显式报文。

_Nooice的博客 1233

MediaPipe+Unity动作捕捉实战:如何用普通摄像头驱动3D人物模型(附避坑指南)

本文详细介绍了如何利用MediaPipe和Unity实现低成本动作捕捉,仅需普通摄像头即可驱动3D人物模型。通过MediaPipe的3D坐标预测能力,结合Unity的骨骼映射优化,解决单目摄像头下的三维重建难题,并提供实战避坑指南和性能优化技巧,帮助开发者快速实现专业级的人物模型驱动效果。

weixin_29047545的博客 356

HashMap源码解析JDK1.8 —————— 开开开山怪

hashMap源码解析 导航 1.影响hashmap性能因素 key的hash实现 loadFactor的设定 2.hashMap的数据结构 3.hashmap的主要方法解析 put() get() resize() remove() containsKey()

String_guai 620

VCS多lib编译实战:如何高效处理Verilog模块重命名与分开编译

本文深入探讨了在复杂SoC设计中,如何利用VCS工具实现多库编译,以高效处理Verilog模块重命名与分开编译的工程难题。通过解析synopsys_sim.setup配置、Config映射机制,并提供完整的自动化脚本实战指南,帮助工程师构建健壮、可维护的编译环境,从而显著提升大型项目的编译效率和代码管理灵活性。

weixin_29169505的博客 357

Reentrantlock和背后金主AQS —————— 开开开山怪

ThreadPoolExecutor源码解析 public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); //在最初的初始化的时候会进行对核心线程池数量的确定 // ...

String_guai 614

Kafka延迟队列实现方案全解析

$ \text{时间轮} = \text{环形数组} + \text{分层设计} $$需要补充代码实例或特定场景的优化方案可进一步细化,此大纲已覆盖核心技术路径。

lz989796的博客 816

从零到英雄:如何用FPGA和AD9288打造你的第一台便携式示波器

本文详细介绍了如何利用FPGA和AD9288 ADC芯片从零开始打造一台高性能便携式示波器。通过硬件架构设计、高速数据采集系统实现及信号处理与显示系统的开发,读者可以掌握400Msps采样率的实现方法,并了解关键器件选型与系统优化技巧。

jjj34的博客 277

Reentrantlock和其搭档Condition —————— 开开开山怪

这篇博客具体来说说Condition,但同时也离不开Reentrantlock,因为我们知道对于synchronize来说,是由Object类配合其进行使用,那么对于Reentrantlock来说则采用的Condition和其进行配合,但不同的是对于一个synchronize只拥有一个阻塞队列,就是我们平时new 一个Object对象,采用Object对象或者是一个Object类作为Synchro...

String_guai 301

TensorFlow_PyTorch实战:构建AI人工智能回归模型

回归分析是机器学习的核心任务之一,用于预测连续型目标变量(如房价、股票价格、用户留存时间等)。基础线性回归模型的数学原理与代码实现多项式回归模型的过拟合处理与正则化技巧自定义数据集加载与数据预处理流程动态图(PyTorch)与静态图(TensorFlow)的编程范式对比模型评估指标与可视化分析方法核心概念:对比两大框架的架构设计,解析回归模型核心组件数学原理:推导线性回归的假设函数、损失函数及优化算法算法实现:提供TensorFlow和PyTorch的完整代码示例项目实战。

AI天才研究院 1429
上一篇: Reentrantlock和背后金主AQS —————— 开开开山怪
下一篇: Reentrantlock和其搭档Condition —————— 开开开山怪
开开开山怪
博客等级 码龄8年 41粉丝 30原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值