Java 并发(五)—— 线程池

news2024/9/21 2:42:36
  • 线程池核心参数?(核心线程数、最大线程数、任务队列)
  • 线程池构造方法中除了保存参数以外还要做什么事?(设置线程工厂、任务拒绝策略)
  • 提交任务时线程池要做什么?(任务执行机制)
  • 创建非核心线程时,线程run方法中的内容是什么?(调用runWorker方法getTask()获取任务,然后执行task.run()方法)
  • 如何将提交的任务交给线程运行?(execute / submit
  • 创建线程时需要区分核心线程和非核心线程吗?(需要)
  • 线程池中线程的任务执行完毕以后是什么状态?(TIDYING状态 / TERMINATED 状态)
  • 线程池中的线程如何回收?(消除线程引用后,由JVM进行回收)

一、创建线程池

1.ThreadPoolExecutor 自定义线程池

继承关系

 内部包含四个构造函数如下:

public class ThreadPoolExecutor extends AbstractExecutorService {
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
}

7个核心参数

  1. corePoolSize:the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set

    (核心线程数大小:不管它们创建以后是不是空闲的。线程池需要保持 corePoolSize 数量的线程,除非设置了 allowCoreThreadTimeOut。)

  2. maximumPoolSize:the maximum number of threads to allow in the pool。

    (最大线程数:线程池中最多允许创建 maximumPoolSize 个线程。)

  3. keepAliveTime:when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating。

    (存活时间:如果经过 keepAliveTime 时间后,超过核心线程数的线程还没有接受到新的任务,那就回收。)

  4. unit:the time unit for the {@code keepAliveTime} argument

    (keepAliveTime 的时间单位。)

  5. workQueue:the queue to use for holding tasks before they are executed.  This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method。

    (存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在这里。它仅仅用来存放被 execute 方法提交的 Runnable 任务。所以这里就不要翻译为工作队列了,好吗?不要自己给自己挖坑。)

  6. threadFactory:the factory to use when the executor creates a new thread。

    (线程工程:用来创建线程工厂。比如这里面可以自定义线程名称,当进行虚拟机栈分析时,看着名字就知道这个线程是哪里来的,不会懵逼。)

  7. handler :the handler to use when execution is blocked because the thread bounds and queue capacities are reached。

    (拒绝策略:当队列里面放满了任务、最大线程数的线程都在工作时,这时继续提交的任务线程池就处理不了,应该执行怎么样的拒绝策略。)

2.Executors 工具类(不推荐)

可以看到内部其实还是调用 ThreadPoolExecutor 实现线程池的创建。

public class Executors {

    //1.固定线程数量的线程池
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    
    //2.只有一个线程的线程池
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

    //3.可根据实际情况调整线程数量的线程池
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

    //4.给定的延迟后运行任务或者定期执行任务的线程池
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }
}

Executors 返回线程池对象的弊端如下:

  • FixedThreadPoolSingleThreadExecutor:使用的是有界阻塞队列是 LinkedBlockingQueue ,其任务队列的最大长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。
  • CachedThreadPool:使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE ,如果任务数量过多且执行速度较慢,可能会创建大量的线程,从而导致 OOM。
  • ScheduledThreadPoolSingleThreadScheduledExecutor :使用的无界的延迟阻塞队列 DelayedWorkQueue ,任务队列最大长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。

二、线程池的生命周期管理

线程池内部使用一个变量 ctl 维护两个值:运行状态(runState)和线程数量 (workerCount)。在具体实现中,线程池将运行状态(runState)、线程数量 (workerCount)两个关键参数的维护放在了一起

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    private static int runStateOf(int c)     { return c & ~CAPACITY; } //计算当前运行状态
    private static int workerCountOf(int c)  { return c & CAPACITY; }  //计算当前线程数量
    private static int ctlOf(int rs, int wc) { return rs | wc; }   //通过状态和线程数生成ctl

ThreadPoolExecutor的运行状态有5种,分别为:

 转换过程



 上面都是些基础知识,下面进入任务提交到回收的全流程:

线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。

线程池的运行主要分成两部分:任务管理、线程管理。

  • 任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:(1)直接申请线程执行该任务;(2)缓冲到队列中等待线程执行;(3)拒绝该任务。
  • 线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。 

四、任务执行机制(生产者)

1.提交任务——executor、submit

“线程池中线程异常后:销毁还是复用?” (qq.com)

2.任务调度——execute

所有任务的调度都是由execute方法完成的。这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {  //线程数<核心线程数
            if (addWorker(command, true))  //增加核心线程
                return;
            c = ctl.get();
        }
        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);  //增加非核心线程
        }
        else if (!addWorker(command, false))
            reject(command);
    }

3.任务缓冲——Queue 

4.任务申请——getTask  

5.任务拒绝

用户可以通过实现 RejectedExecutionHandler 接口去定制拒绝策略,也可以选择JDK提供的四种已有拒绝策略 

四、Worker 线程管理(消费者)

1.线程池内部类——Worker

线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker。


Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。

  • thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务;
  • firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。

Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反应线程现在的执行状态。

  • lock方法一旦获取了独占锁,表示当前线程正在执行任务中。 如果正在执行任务,则不应该中断线程。
  • 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断。 线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。
  private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        final Thread thread;

        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        // 通过ThreadFactory来创建线程
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        // 线程执行任务
        public void run() {
            runWorker(this);
        }

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        //AQS 实现独占锁
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.
        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

2.Worker线程增加——addWorker

图片中的上面一排是核心线程自带firstWorker任务执行,下面则是非核心线程取任务执行,由上面的executor方法里也可以看出来。

    private final ReentrantLock mainLock = new ReentrantLock();

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (int c = ctl.get();;) {
            // Check if queue empty only if necessary.
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP)
                    || firstTask != null
                    || workQueue.isEmpty()))
                return false;

            for (;;) {
                if (workerCountOf(c)
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateAtLeast(c, SHUTDOWN))
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                //加锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int c = ctl.get();

                    if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        if (t.getState() != Thread.State.NEW)
                            throw new IllegalThreadStateException();
                        workers.add(w);   //创建线程
                        workerAdded = true;
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }

线程池中的addWorker方法,该方法的功能就是增加一个线程,该方法不考虑线程池是在哪个阶段增加的该线程,这个分配线程的策略是在上个步骤完成的,该步骤仅仅完成增加线程,并使它运行,最后返回是否成功这个结果。addWorker方法有两个参数:firstTask、core。firstTask参数用于指定新增的线程执行的第一个任务,该参数可以为空;core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize,其执行流程如下图所示: 

3.Worker线程执行任务——runWorker

Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反应线程现在的执行状态。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //1.while循环不断地通过getTask()方法获取任务
            //2.getTask()方法从阻塞队列中取任务
            while (task != null || (task = getTask()) != null) {
                //AQS实现独占锁
                w.lock();
                //3.如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    try {
                        //4.执行任务
                        task.run();
                        afterExecute(task, null);
                    } catch (Throwable ex) {
                        afterExecute(task, ex);
                        throw ex;
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //5.如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程
            processWorkerExit(w, completedAbruptly); 
        }
    }

4.Worker线程回收——processWorkerExit

    private final ReentrantLock mainLock = new ReentrantLock();
    private final HashSet<Worker> workers = new HashSet<>();

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);  //将线程移出线程池
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

线程池中线程的销毁依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。

事实上,在这个方法中,将线程引用移出线程池就已经结束了线程销毁的部分。



五、动态设置线程池参数

如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答。 (qq.com)



六、参考

 Java线程池实现原理及其在美团业务中的实践 - 美团技术团队 (meituan.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2048986.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Golang Map 深度剖析:原理、实践与面试要点

嘿,小伙伴们!我是 k 哥。今天,咱们来聊聊 Map 。 在 Go 语言这个神奇的世界里,Map 这个有点神秘的数据结构一直都是开发者们特别关注的。 你是不是在用 Map 的时候,对它里面咋工作的感到好奇?是不是碰到复杂操作的时候,特别想弄明白它背后的原理?别着急,今天这篇文章…

Java流程控制06:嵌套for循环

本节教学视频链接&#xff1a;https://www.bilibili.com/video/BV12J41137hu?p41&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5https://www.bilibili.com/video/BV12J41137hu?p41&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 在Java中&#xff0c;‌嵌套for循环是指将…

使用三菱PLC源码进行PLC读取写入操作

安装 MX Component 。 我的安装地址在&#xff1a; 打开 utl 文件夹下的 Communication Settings Utility 执行。 配置PLC 添加当前需要配置的PLC 注意 logical station Namber 就是程序里需要对接的逻辑站点编号 5.配置选择对应的COM操作选择对应的cpu型型号&#xff0c;…

Ah That‘s Hawt

目录 一、题目 二、思路 三、payload 3.1 方案一 3.1 方案二 四、思考与总结 一、题目 <!-- Challenge --> <h2 id"will"></h2> <script>smith (new URL(location).searchParams.get(markassbrownlee) || "Ah Thats Hawt")sm…

甄选系列“论软件开发过程RUP及其应用”,软考高级论文,系统架构设计师论文

论文真题 RUP(Rational Unified Process)是IBM公司的一款软件开发过程产品,它提出了一整套以UML为基础的开发准则,用以指导软件开发人员以UML为基础进行软件开发。RUP汲取了各种面向对象分析与设计方法的精华,提供了一个普遍的软件过程框架,可以适应不同的软件系统、应用…

ant -design 框架以及具体调试

1.介绍 Ant-Design-Vue 是一个基于 Ant Design 设计体系的 Vue 实现。Ant Design 是由阿里巴巴开源的一个企业级 UI 设计语言&#xff0c;旨在提升用户体验和开发效率。Ant-Design-Vue 将 Ant Design 的设计理念和组件库带入了 Vue 生态系统&#xff0c;使得开发者能够在 Vue …

RabbitMQ集群 - 普通集群搭建、宕机情况

文章目录 RabbitMQ 普通集群概述集群搭建数据准备启动容器宕机情况 RabbitMQ 普通集群 概述 1&#xff09;普通模式中所有节点没有主从之分&#xff0c;所有节点的元数据&#xff08;交换机、队列、绑定等&#xff09;都是一致的. 例如只要有任意一个节点上面 新增交换机&…

迈出Python自动化测试的第一步

一、思考❓❔ 1.什么是性能自动化测试? 性能 系统负载能力超负荷运行下的稳定性系统瓶颈 自动化测试 使用程序代替手工提升测试效率 性能自动化 使用代码模拟大批量用户让用户并发请求多页面多用户并发请求采集参数&#xff0c;统计系统负载能力生成报告 2.Python中的性能自…

C++竞赛初阶L1-12-第五单元-while(27~28课)531: T456440 含 k 个 3 的数

题目内容 输入两个正整数 m 和 k&#xff0c;其中 1<m≤1015&#xff0c;1<k≤15 &#xff0c;判断 m 是否恰好含有 k 个 3&#xff0c;如果满足条件&#xff0c;则输出 YES&#xff0c;否则&#xff0c;输出 NO。 输入格式 输入一行&#xff0c;为两个整数 m,k&#x…

【js面试题】js原型,原型链?有什么特点

在 JavaScript 中&#xff0c;原型&#xff08;Prototype&#xff09;和原型链&#xff08;Prototype Chain&#xff09;是实现继承和共享属性与方法的核心机制。理解它们对于深入掌握 JavaScript 的对象模型非常重要。 原型&#xff08;Prototype&#xff09; 每个 JavaScri…

24年上半年天融信营收缩减1.8亿,亏损2.06亿

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…

建筑三剑客:平面、剖面与立面图解析

平面图、剖面图与立面图是建筑学中不可或缺的工具&#xff0c;它们共同构成了建筑设计的基础。平面图展示了建筑物的顶部视图&#xff0c;详细标出了房间布局和空间关系。剖面图则揭示了建筑的内部结构&#xff0c;包括楼层分布和垂直交通。而立面图呈现了建筑的外观&#xff0…

【学习笔记】Matlab和python双语言的学习(一元线性回归)

文章目录 前言一、一元线性回归回归分析的一般步骤一元线性回归的基本形式回归方程参数的最小二乘法估计对回归方程的各种检验估计标准误差的计算回归直线的拟合优度判定系数显著性检验 二、示例三、代码实现----Matlab四、代码实现----python回归系数的置信区间公式残差的置信…

入门MySQL数据库

目录 一、MySQL的安装&#xff08;以5.7版本为例&#xff09; 1. 一路默认安装即可&#xff0c;注意root密码。 2.配置环境变量 3.登录数据库 二、指令 1.数据库 2.数据表 3.约束 4.增删改查 1>查 2>增 3>改 4>删 5.数据库用户 6.外键 1>创建添加外…

实验5:数码管实验,51单片机

8个数码管 LED1-LED8分别有P22,P23,P24的A,B,C控制 C B A 000 0-1 001 1-2 010 2-3 011 3-4 101 4-5 110 6-7 111 7-8 共阴极数码管,八段 0-F编码 硬件图 对应P0口 main.c #include<reg52.h>typedef unsigned int u16; typedef unsigned char u8;#d…

RPA在政务领域的发展前景

随着信息技术的迅猛发展&#xff0c;政务领域也在不断探索创新&#xff0c;以提升政府服务的质量和效率。RPA作为一种自动化技术&#xff0c;打破了传统政务服务人工操作的局限&#xff0c;协助基层人员更高效准确地完成录入、审查、校对和数据汇总等各项繁琐的工作&#xff0c…

第1节 安装Flask

我们以Thonny4为例&#xff1a; flask是第一个第三方库。与其他模块一样&#xff0c;安装时可以直接使用python的pip命令实现。 一、找到你的安装目录 这是我的安装目录&#xff1a; D:\thonney4\scripts 二、执行pip pip install Flask

LabVIEW VI 多语言动态加载与运行的实现

在多语言应用程序开发中&#xff0c;确保用户界面能够根据用户的语言偏好动态切换是一个关键需求。本文通过分析一个LabVIEW程序框图&#xff0c;详细说明了如何使用LabVIEW中的属性节点和调用节点来实现VI&#xff08;虚拟仪器&#xff09;界面语言的动态加载与运行。此程序允…

人像修复-DB双曲线

相对于中性灰图层修复&#xff0c;不容易掉色&#xff0c;光影过渡更均匀&#xff0c;适合大范围调整光影&#xff0c;而中性灰适合更调整细节主要用于修饰光影均匀过渡&#xff0c;先大范围修饰整体&#xff0c;再局部细节修饰 建立明度观察层&#xff08;渐变映射曲线&#x…

Xilinx系ZYNQ学习笔记(二)ZYNQ入门

系列文章目录 文章目录 系列文章目录前言简单介绍简称 xc7z020型号FPGAZYNQ实操通用IO点亮LED灯硬件逻辑基础 前言 简单入门一下ZYNQ是何种架构&#xff0c;如何编程&#xff0c;至于深入了解应该要分开深入学习Linux和FPGA 简单介绍 其基本架构都是在同一个硅片上集成 FPGA …