【深入理解 线程池】

news2025/1/21 6:00:00

深入理解 线程池

  • 介绍
  • 源码学习
    • 线程池的类继承体系
    • ThreadPoolExector
      • 核心数据结构
      • 核心配置参数
        • 线程池的执行流程如图:
      • 线程池的优雅关闭
        • 线程池的生命周期
        • 正确关闭线程池的步骤
      • 任务的提交过程分析
      • 任务的执行过程
        • shutdonw() 与任务执行过程综合分析
        • shutdonwNow() 与任务执行过程综合分析
  • 总结

介绍

线程池(Thread Pool) 把一个或多个线程通过统一的方式进行调度和重复使用的技术(采用池化思想)。
优点:
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
3.提高线程的可管理性

源码学习

线程池的类继承体系

在这里插入图片描述
在类图中,有两个核心的类。ThreadPoolExectorScheduledThreadPoolExecutor。后者不仅可以执行某个任务,还可以周期性地执行任务。向线程池中提交的每个任务,都必须实现Runnable接口。通过最上面的Executor 接口中的execute(Ruunable task)向线程池提交任务。同时,在ExecutorService中,定义了线程池的关闭接口shutdown。还定义了可以有返回值的任务。也就是Callable

ThreadPoolExector

核心数据结构

public class ThreadPoolExecutor extends AbstractExecutorService {
     //状态变量
     private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
     //存放任务的阻塞队列
     private final BlockingQueue<Runnable> workQueue;
     //对线程池内部各种变量进行互斥访问控制
     private final ReentrantLock mainLock = new ReentrantLock();
     private final Condition termination = mainLock.newCondition();
     //线程集合
     private final HashSet<Worker> workers = new HashSet<Worker>();
       ......
       }

每一个线程是一个Worker对象。Worker继承自AQS。具有锁的一些特性,对于完成线程池的关闭和执行任务起到关键作用。

 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;

        /** Thread this worker is running in.  Null if factory fails. */
        //Worker 封装的线程
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        // Worker 接受到的第一个任务
        Runnable firstTask;
        /** Per-thread task counter */
        //Worker 执行完毕的任务个数
        volatile long completedTasks;
          .....
}

核心配置参数

 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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

面试常考:
corePoolSize:在线程池中始终维护的线程个数。
maxPoolSize:在corePoolSize 已满,队列也满的情况下,扩充线程到该值。
keepAliveTime/TimeUnit: maxPoolSize 中的空闲线程,销毁所需要的时间。总线程回收至corePoolSize.
TimeUnit 是时间单位。
blockingQueue:线程池所用的队列类型。一定要设置队列大小。
threadFactory: 线程创建工厂,可以自定义。一般我们采用默认值。
RejectedExecutionHandler:corePoolSize 已满,队列已满,maxPoolSize已满,最后的拒绝策略。

线程池的执行流程如图:

在这里插入图片描述

首先是判断corePoolSize,其次判断blockingQueue是否已满,接着判断maxPoolSize.最后使用拒绝策略。所以 一定要设置blockingQueue的大小。不然会一直往队列中塞任务,撑爆服务器内存然后宕机。

线程池的优雅关闭

线程池的关闭对比于线程的关闭,更加复杂。 比如 当关闭一个线程池的时候,有的线程正在执行某个任务,有的调用者正在向线程池提交任务,并且队列中还有未执行的任务。因此,关闭过程不可能是立刻马上关闭,需要有一个平滑的过渡,这里就涉及线程池的完整生命周期管理。

线程池的生命周期

线程池中,把线程数量(workCount)和线程池状态(runState)这两个变量打包存储在一个字段里,即ctl变量。如下图,最高位的三位存储线程池状态,其余29位存储线程个数。在jdk6中,这两个变量是分开存储。
在这里插入图片描述

    //初始化时,线程池状态为RUNNING,线程数为0
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //最高的三位表示线程池的状态
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    //线程池的5钟状态
    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;

    // Packing and unpacking ctl
    //从ctl 中分别解包出runState和workerCount 
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    //rs 即runState,wc即workerCount,两个变量打包成ctl一个变量
    private static int ctlOf(int rs, int wc) { return rs | wc; }

从上面的代码可以看出,ctl变量被拆成两半,最高的3位用来表示线程池的状态,低的29位表示线程的个数。线程池的状态有5种。分别是RUNNING,SHUTDOWN,STOP和TERMINATED.
5种状态的迁移过程如图:
在这里插入图片描述
线程池有两个关闭函数,shutdown()和shutdownNow(),这两个函数会让线程池切换到不同的状态。在队列为空,线程池也为空之后,进入TIDYING状态。最后执行一个钩子函数terminated(),进入TERMINATED状态。线程池才 “结束生命”。
这里的状态迁移,只能从小到大迁移,不能逆向迁移。

正确关闭线程池的步骤

通过上面的分析,我们了解到 线程池的关闭需要一个过程,在调用shutdown 或者shutdownNow之后,线程池并不会立即关闭,接下来需要调用awaitTermination来等待线程池关闭。关闭线程池的正确步骤如下:
调用完 shutdown或者 shutdownNow后。然后循环调用 awaitTermination()方法来判断线程池的状态。

executor.shutdown();
//调用shutdown()后,调用 awaitTermination
try{
	boolean loop=true;
	do{
    loop=!executor.awaitTermination(2,TimeUnit.SECONDS);
    //阻塞,直到线程池里所有的任务结束	
  }while(loop)
}catch(InterruptedException e){
	
}

awaitTermination 函数
不断循环判断线程池是否达到了最终状态TERMINATED,如果达到了,就返回,如果不是,则通过termination条件变量阻塞一段时间。"苏醒"之后继续判断。

public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        	//不断循环
            for (;;) {
            	//判断线程池的状态是否是TERMINATED
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                    //如果不是,进行阻塞
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

shutdown 和shutdownNow的区别
1.前者不会清空任务队列,会等待所有任务执行完成,后者会直接清空队列
2.前者只会中断空闲的线程,后者会中断所有的线程。

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
             //判断是否有权限关闭线程池
            checkShutdownAccess();
            //设置状态为SHUTDOWN
            advanceRunState(SHUTDOWN);
            //只中断空闲的线程
            interruptIdleWorkers();
            //钩子函数,目前是空的,为自定义的线程池个性化扩展
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
  public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //判断是否有权限关闭线程池
            checkShutdownAccess();
            //设置状态为STOP
            advanceRunState(STOP);
             //中断所有线程
            interruptWorkers();
            //清空队列
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

继续分析下 interruptIdleWorkers()中断空闲线程和 interruptWorkers()中断所有线程

  private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }
    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //状态是是非interrutped,并且能拿到锁。work也是继承AQS,说明是空闲的
                //此时才执行interupt,中断信号
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }
  void interruptIfStarted() {
            Thread t;
            //只要线程启动,并且状态不是interrupted。就执行中断信号
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

shutdown和shutdownNow 最后都执行了tryTerminate();
tryTerminate不会强行终止线程池,只是做了检测。当 workerCount为0,workerQueue为空时,先把状切换为TIDYING。然后调用钩子函数terminated,随后把状态改成TERMINATED。最后执行 termination.sinaAll().通知前面阻塞在awaitTermination的所有调用线程。

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
			//当workQueue为空,workCount为0时,才会到这
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
            		//cas 修改状态为 TIDYING
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                    	//调用钩子函数
                        terminated();
                    } finally {
                    	//设置状态为 TERMINATED
                        ctl.set(ctlOf(TERMINATED, 0));
                        //唤醒所有线程--在awaitTermination
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

任务的提交过程分析

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的值
            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);
    }

addWorker 此函数用于开一个新的线程,如果第二个参数core为true,则用corePoolSize作为上界,如果为false,则用maxPoolSize作为上界。

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            //如果线程池的状态为SHUTDOWN,说明线程池进入了关闭过程
            // 并且 1. 线程状态已经大于 SHUTDOWN 就直接返回 false,
            //      2.新加的任务 不是null.也不能加任务了,则直接返回 false
            //     3. 队列是空的,也不加任务了,直接返回 false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                	//线程数超过了上界,则不会创建,直接返回false;
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                    //worker加一,则跳出循环
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                //重新判断c.如果值不相等,说明有其他线程增加了进来。则需要从新循环
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

		//到这里说明worker加1了,后面的逻辑就是具体实例化一个worker.
        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 rs = runStateOf(ctl.get());
					//再次校验rs 的值。即线程池的状态
					//线程池的状态还是 RUNNING,
					//如果是 SHUTDOWN.可以加null任务
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            //线程还没启动,此时是alive().则抛出异常
                            throw new IllegalThreadStateException();
                        //把线程加入到线程集合
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //此时才启动该线程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
            //如果加入线程失败。则把workerCount减一
                addWorkerFailed(w);
        }
        return workerStarted;
    }

任务的执行过程

在上面的任务提交过程中,可能会开启一个新的Worker,并把任务本身作为firstTask赋给该Worker。但对于一个Worker来说,不是只执行一个任务,而是源源不断地从队列中取任务执行,这是一个不断循环的过程。

    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;

        /** Thread this worker is running in.  Null if factory fails. */
        //对于的线程
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        //对于的任务
        Runnable firstTask;
        /** Per-thread task counter */
        //统计线程完成的任务数量
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            //初始值设置的是-1
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            //调用的是ThreadPoolExecutor 的runWorker方法
            runWorker(this);
        }
      }

runWorker

 //运行的任务有两种类型 一种是线程数不到核心线程数或者队列满了线程数不到最大线程数,直接new Work执行
// 从队列中取
 final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        // 从worker中取的任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        	//不断循环从队列中获取任务执行
        	//task 首先从work中取,取不到然后再从队列中取(getTask())
            while (task != null || (task = getTask()) != null) {
                //执行任务先加锁。此处对应了shutdown 来判断线程是否是空闲时的操作tryLock.
                //如果能获取到锁,说明没有任务执行,线程是空闲的。
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                //对线程池和线程状态的校验判断,
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    //不符合就interupt,给自己发送中断信号
                    wt.interrupt();
                try {
                    //任务之前的钩子函数,目前是空
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //执行任务
                        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;
                    //完成任务数加1
                    w.completedTasks++;
                    //释放锁
                    w.unlock();
                }
            }
            //判断是正常退出还是异常退出,用于finally里面里面进行worker退出的逻辑处理
            completedAbruptly = false;
        } finally {
            //worker退出
            processWorkerExit(w, completedAbruptly);
        }
    }

shutdonw() 与任务执行过程综合分析

把任务的执行过程和上面的线程池的关闭过程结合起来进行分析。当调用shutdown()的时候,可能会出现几种情况
场景一:
当调用shutdown时候,所有线程都处于空闲状态。
这意味着任务队列一定是空的。此时,所有线程都会阻塞在getTask()的地方,然后,所有线程都会收到interruptIdleWorkers()发过来的中断信号。getTask()会响应中断,然后返回null.所有Worker都会退出while循环,然后执行processWorkerExit;

场景二:
当调用shutdown时,所有线程都处于忙碌状态
此时队列可能为空,也可能是非空的,interruptIdleWorkers()内部的tryLock调用失败。什么都不会做,直至队列任务为空。interruptIdleWorkers()内部的tryLock调用成功。此时和场景一一样。

场景三:
当调用shutdown时,部分线程忙碌,部分线程空闲。
有部分线程空闲,说明队列是空的。忙碌的线程按照场景一处理,不忙碌的线程按照场景一处理。

getTask()函数

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            //rs >= SHUTDOWN 说明调用了shutdown。
            // 并且队列为空 或者 rs >= STOP 调用了shutdownNow ,则返回null 
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
            //1.队列为空,就会阻塞在此处的poll或者take。poll 带超时时间。take不带超时时间。
            //2.一旦收到中断信号,就会进入catch代码,设置 timedOut=false;
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                //可以响应 interruptIdleWorkers 发出的中断信号 
                timedOut = false;
            }
        }
    }


shutdonwNow() 与任务执行过程综合分析

shutdownNow()比较粗暴。和shutdown()相比,多了一个清除队列的操作。少了一些判断而已。

总结

整体来讲,线程池的设计还是比较复杂的。本文首先从整体架构来介绍了线程池,然后逐步从线程池的提交执行和关闭等操作进行了源码分析。希望能对大家理解线程池有所帮助。

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

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

相关文章

python3.6 处理报错free(): invalid pointer

在运行脚本的时候遇到了这个报错&#xff0c;我在笔记本的win10 python3.7上正常运行&#xff0c;把程序考到服务器报了这个错&#xff0c;free(): invalid pointer 脚本里写了异常处理&#xff0c;用的是纯净的虚拟环境&#xff0c;所以我感觉问题是出在系统环境上 在网上搜…

Linux:tcp socket客户端和服务器端代码

服务器端代码&#xff1a; #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <arpa/inet.h> #include <sys/un.h> #include <sy…

手工数据采集耗时耗力?Smartbi数据填报实现数据收集分析自动化

企业在日常经营管理过程中&#xff0c;往往需要收集很多内外部的信息&#xff0c;清洗整理后再进行存储、分析、呈现、决策支持等各种作业&#xff0c;如何高效收集结构化数据是企业管理者经常要面对的问题。传统手工的数据采集方式不仅耗费了大量人力时间成本&#xff0c;还容…

爽,我终于掌握了selenium图片滑块验证码

因为种种原因没能实现愿景的目标&#xff0c;在这里记录一下中间结果&#xff0c;也算是一个收场吧。这篇文章主要是用selenium解决滑块验证码的个别案列。 思路&#xff1a; 用selenium打开浏览器指定网站 将残缺块图片和背景图片下载到本地 对比两张图片的相似地方&#x…

【含源码】用python做游戏有多简单好玩

有很多同学问我还有其他什么小游戏吗&#xff0c;游戏是怎么做的&#xff0c;难不难。我就用两篇文章来介绍一下&#xff0c;如何使用Python做游戏。 兔子与灌 俄罗斯方块 休闲五子棋 走迷宫 推箱子 消消乐 超多小游戏玩转不停↓ 更多小游戏可以评论区讨论哦&#xff0c;喜欢…

C中AES_cbc_encrypt加密对应java中的解密

前言知识&#xff1a; 1.AES&#xff08;Advanced Encryption Standard&#xff09;高级加密标准&#xff0c;作为分组密码&#xff08;把明文分成一组一组的&#xff0c;每组长度相等&#xff0c;每次加密一组数据&#xff0c;直到加密完整个明文&#xff09;。 2.在AES标准…

C#基础教程12 数组

文章目录 C# 数组(Array)C# 中的数组声明数组初始化数组赋值给数组访问数组元素C# 数组细节C# 数组(Array) 数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合。 声明数组变量并不是声明 number0、number1…

【设计模式】工厂模式

工厂模式 所谓工厂&#xff0c;顾名思义&#xff0c;就是创建出一类相似的产品的&#xff0c;工厂模式可以帮我们创建各个复杂/简单对象。属于创建型模式。 工厂模式分为三类: 简单工厂工厂方法抽象工厂 简单工厂 比方说我们需要根据配置文件去解析配置&#xff0c;不同后…

html5播放器禁止拖拽、视频禁止拖动的实例

阿酷TONY / 2023-3-8 / 长沙html5播放器禁止拖拽功能,常用于场景&#xff1a;企业培训、在线教学内容禁止学员拖动视频进行观看。应用代码实例&#xff1a;<div id"player"></div> <script src"//player.polyv.net/script/player.js">&l…

pytest初识

一、单元测试框架 &#xff08;1&#xff09;什么是单元测试框架&#xff1f; 单元测试是指在软件开发中&#xff0c;针对软件的最小单元&#xff08;函数、方法&#xff09;进行正确性的检查测试 &#xff08;2&#xff09;单元测试框架 java&#xff1a;junit和testng pytho…

Windows SSH 配置和SCP的使用

使用用户界面安装 ssh 功能 要在 Windows 10/11 上启用 SSH 服务器&#xff0c;请按照以下步骤操作&#xff1a; 按“Windows 键 I”打开“设置”菜单&#xff0c;然后选择“应用程序”。在左侧菜单栏中选择“应用和功能”。从列表中选择“可选功能”。 点击“添加功能”按钮…

[数据结构]:15-堆排序(顺序表指针实现形式)(C语言实现)

目录 前言 已完成内容 堆排序实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-PSeqListFunction.cpp 04-SortCommon.cpp 05-SortFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为C语言代码…

android h5考勤管理系统myeclipse开发mysql数据库编程服务端java计算机程序设计

一、源码特点 android h5考勤管理系统是一套完善的WEBandroid设计系统&#xff0c;对理解JSP java&#xff0c;安卓app编程开发语言有帮助&#xff08;系统采用web服务端APP端 综合模式进行设计开发&#xff09;&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主…

平板触控笔要原装的吗?开学季必备电容笔推荐

如今对那些把ipad当做学习工具的用户而言&#xff0c;Apple Pencil就显得尤为重要了。但由于Apple Pencil的售价实在太高&#xff0c;让学生党望而却步。因此&#xff0c;最好的办法就是选择平替电容笔。我是一个ipad设备的忠实用户&#xff0c;同时也是一个数码热衷者&#xf…

MySQL增量备份和全量备份

1 全量备份 1.1 创建用于备份的目录 mkdir /root/mysql.backup1.2 创建存入备份文件的目录 mkdir /root/mysql.backup/data1.3 进入备份目录&#xff0c;创建备份脚本 cd /root/mysql.backupvim mysqlBackuoShell.sh#!/bin/bash #保存备份个数,31条 number31 #备份保存路…

Infineon Aurix 系列网络安全概述

目录 硬件安全 其它 1999年&#xff0c;英飞凌推出了第一代AUDO(汽车统一处理器)系列。基于统一的以RISC/MCU/DSP处理器为核心&#xff0c;这款32位TriCore™微控制器是一款强大的计算机器。其不断改进和优化TriCore的概念&#xff0c;最终形成了现在的第六代TriCore™,由于其…

ACWING蓝桥杯每日一题python(持续更新

ACWing蓝桥杯每日一题 一直没时间去总结算法&#xff0c;终于有空可以总结一下刷的acwing了&#xff0c;因为没时间所以最近只刷了ACWING的蓝桥杯每日一题。。。真是该死 1.截断数组 首先我们要知道&#xff0c;如果sum(a)不能被3整除或者len(a) < 3 &#xff0c;那么他肯…

Android使用移动智能终端补充设备标识获取OAID

官网http://www.msa-alliance.cn/col.jsp?id120首先到官网注册账号&#xff0c;申请下载相关sdk和授权证书2.把 oaid_sdk_x.x.x.aar 拷贝到项目的 libs 目录&#xff0c;并设置依赖&#xff0c;其中x.x.x 代表版本号3.supplierconfig.json 拷贝到项目 assets 目录下&#xff0…

keepalived实现nginx高可用

文章目录前言keepalived简介软件架构简单理解环境准备一、keepalived安装1.1 下载keepalived 安装包1.2 解压/下载依赖1.3 编译1.4 创建软连接1.5 增加系统服务1.6 启动keepalived二、实现Nginx高可用2.1 创建keepalived配置文件2.2 Nginx监控脚本2.4 重启keepalived2.5 查看虚…

自己DIY装机后,如何使用U盘装系统

最近自己整了一台主机&#xff0c;然后需要装一下系统&#xff0c;这边就讲一下如何用U盘给新电脑装系统。 安装Windows 10 光盘映像 首先你需要一个内存大于8GB的U盘&#xff0c;并且是纯净的&#xff0c;里面没有东西。 这边以Windows 10 为例&#xff1a; 百度搜索 下载…