【源码解析】聊聊线程池 实现原理与源码深度解析(一)

news2025/1/19 11:04:27

一、Java 线程池 实现原理与源码深度解析

架构

在这里插入图片描述
总揽线程池设计,其实可以发现都是符合顶层的接口设计,中间抽象类,最终是实际工作类

使用示例

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("我是要执行的任务,正在处理中");
    }

    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Runnable runnable2 = new MyRunnable();

        ExecutorService threadPool = Executors.newFixedThreadPool(1);
        threadPool.execute(runnable);
        threadPool.execute(runnable2);

        try {
            threadPool.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Executor接口

public interface Executor {

    void execute(Runnable command);
}

只定义了一个接口方法,核心就是处理具体的工作任务。

ExecutorService

public interface ExecutorService extends Executor {

    // 关闭线程池,已提交的任务继续执行,不接受继续提交新任务
    void shutdown();

    // 关闭线程池,尝试停止正在执行的所有任务,不接受继续提交新任务
    // 它和前面的方法相比,加了一个单词“now”,区别在于它会去停止当前正在进行的任务
    List<Runnable> shutdownNow();

    // 线程池是否已关闭
    boolean isShutdown();

    // 如果调用了 shutdown() 或 shutdownNow() 方法后,所有任务结束了,那么返回true
    // 这个方法必须在调用shutdown或shutdownNow方法之后调用才会返回true
    boolean isTerminated();

    // 等待所有任务完成,并设置超时时间
    // 我们这么理解,实际应用中是,先调用 shutdown 或 shutdownNow,
    // 然后再调这个方法等待所有的线程真正地完成,返回值意味着有没有超时
    boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException;

    // 提交一个 Callable 任务
    <T> Future<T> submit(Callable<T> task);

    // 提交一个 Runnable 任务,第二个参数将会放到 Future 中,作为返回值,
    // 因为 Runnable 的 run 方法本身并不返回任何东西
    <T> Future<T> submit(Runnable task, T result);

    // 提交一个 Runnable 任务
    Future<?> submit(Runnable task);

    ......
}

AbstractExecutorService

其实java中框架的特点都是设计一个顶层接口,然后有基于顶层接口设计相关的抽象基础类,实现一些基础的功能。AbstractExecutorService最核心的方法就是newTaskFor() 将任务包装成一个FutureTask,FutureTask间接实现了Runnable类。但是最核心的方法,execute AbstractExecutorService是没有实现的。而是需要子类进行实现。

public abstract class AbstractExecutorService implements ExecutorService {

    /**
     * 将任务包装成FutureTask任务。
     */
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

    
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

   
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        //1.将任务包装成RunableFuture对象,由于RunnableFuture是实现Runable类,所以execute的参数是一个可拓展的类型
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        //2,交给具体的执行器进行实现
        execute(ftask);
        return ftask;
    }

   
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
}

ThreadPoolExecutor

我们知道通过Executors.newXXX 可以创建固定线程池、可缓存线程池等,但是最终指向的都是ThreadPoolExecutor类

    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;
        //最大线程数
        //默认使用核心线程,超过直接,将任务存储到任务队列中,如果任务队列中也满了,查看是否
        //超过最大线程数,如果没有超过就new一个线程执行任务。
        //异常情况,
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        //线程工厂,一般推荐自定义的
        this.threadFactory = threadFactory;
        //拒绝策略
        this.handler = handler;
    }
  • corePoolSize

线程池中的核心线程数。

  • maximumPoolSize

最大线程数,线程池允许创建的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue。

无界队列会一直存储任务,所以不会创建新的线程执行任务。

  • workQueue

用来保存等待被执行的任务的阻塞队列. 在JDK中提供了如下阻塞队列:

(1) ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
    (2) LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
    (3) SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
    (4) priorityBlockingQuene:具有优先级的无界阻塞队列;

  • keepAliveTime

空闲线程的保活时间,如果某线程的空闲时间超过这个值都没有任务给它做,那么可以被关闭了。注意这个值并不会对所有线程起作用,如果线程池中的线程数少于等于核心线程数 corePoolSize,那么这些线程不会因为空闲太长时间而被关闭,当然,也可以通过调用 allowCoreThreadTimeOut(true)使核心线程数内的线程也可以被回收;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止。

  • unit

keepAliveTime的单位

  • threadFactory

用于生成线程,一般我们可以用默认的就可以了。通常,我们可以通过它将我们的线程的名字设置得比较可读一些,如 Message-Thread-1, Message-Thread-2 类似这样。

  • handler

线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

AbortPolicy:直接抛出异常,默认策略;
      CallerRunsPolicy:用调用者所在的线程来执行任务;
      DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
      DiscardPolicy:直接丢弃任务;
    当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

拒绝策略

    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
    
    
     public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }
    
    
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {

        public DiscardOldestPolicy() { }


        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }
    
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }
      
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

属性

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

// 这里 COUNT_BITS 设置为 29(32-3),意味着前三位用于存放线程状态,后29位用于存放线程数
private static final int COUNT_BITS = Integer.SIZE - 3;

// 000 11111111111111111111111111111
// 这里得到的是 29 个 1,也就是说线程池的最大线程数是 2^29-1=536870911
// 以我们现在计算机的实际情况,这个数量还是够用的
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// 我们说了,线程池的状态存放在高 3 位中
// 运算结果为 111跟29个0:111 00000000000000000000000000000
private static final int RUNNING    = -1 << COUNT_BITS;
// 000 00000000000000000000000000000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 001 00000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS;
// 010 00000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;
// 011 00000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS;

// 将整数 c 的低 29 位修改为 0,就得到了线程池的状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 将整数 c 的高 3 为修改为 0,就得到了线程池中的线程数
private static int workerCountOf(int c)  { return c & CAPACITY; }

private static int ctlOf(int rs, int wc) { return rs | wc; }

private static boolean runStateLessThan(int c, int s) {
    return c < s;
}

private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

属性主要是通过描述线程池的状态,

  • RUNNING:这个没什么好说的,这是最正常的状态:接受新的任务,处理等待队列中的任务
  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务
  • STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程
  • TIDYING:所有的任务都销毁了,workCount 为 0。线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()
  • TERMINATED:terminated() 方法结束后,线程池的状态就会变成这个

在这里插入图片描述

Worker

将执行的任务封装成一个Woker,内部通过引用真正执行任务的线程和实际的任务。

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {

        /** 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;

        /**
         * 将运行任务封装成worker
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            //调用线程工作创建一个新的线程,创建的线程到时候用来执行任务
            this.thread = getThreadFactory().newThread(this);
        }

        //任务实际执行的方法
        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }
    }

execute

判断是否创建线程

    public void execute(Runnable command) {
        // 若任务为空,则抛 NPE,不能执行空任务
        if (command == null) {
            throw new NullPointerException();
        }
        // 表示线程数的状态和线程数的整数
        int c = ctl.get();
        // 若工作线程数小于核心线程数,则创建新的线程,并把当前任务 command 作为这个线程的第一个任务
        //一个新创建的线程池,里面没有线程
        if (workerCountOf(c) < corePoolSize) {
            //如果当前线程小于核心线程数,则创建核心线程数
            if (addWorker(command, true)) {
                return;
            }
            //添加核心线程数失败了,重新获取。可能存在同时多个线程任务创建任务,A,B,C
            c = ctl.get();
        }
        /**
         * 至此,有以下两种情况:
         * 1.当前工作线程数大于等于核心线程数 
         * 2.新建核心线程失败
         * 此时会尝试将任务添加到阻塞队列 workQueue
         */
        // 若线程池处于 RUNNING 状态,将任务添加到阻塞队列 workQueue 中
        if (isRunning(c) && workQueue.offer(command)) {
            // 再次检查线程池标记
            int recheck = ctl.get();
            // 如果线程池已不处于 RUNNING 状态,那么移除已入队的任务,并且执行拒绝策略
            if (!isRunning(recheck) && remove(command)) {
                // 任务添加到阻塞队列失败,执行拒绝策略
                reject(command);
            }
            // 如果线程池还是 RUNNING 的,并且线程数为 0,那么开启新的线程
            else if (workerCountOf(recheck) == 0) {
                addWorker(null, false);
            }
        }
        /**
         * 至此,有以下两种情况:
         * 1.线程池处于非运行状态,线程池不再接受新的线程
         * 2.线程处于运行状态,但是阻塞队列已满,无法加入到阻塞队列
         * 此时会尝试以最大线程数为限制创建新的工作线程
         */
        else if (!addWorker(command, false)) {
            // 任务进入线程池失败,执行拒绝策略
            reject(command);
        }
    }

执行任务是通过threadPool.execute(runnable); 进行执行的,这里其实就是核心的流程。

addWorker

主要的作用就是将要执行的任务添加到set中

    //第一个参数是要执行的任务,第二个参数是是否是核心线程
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        //经过判断处理,给工作线程数+1
        for (;;) {
            int c = ctl.get(); //线程个数
            int rs = runStateOf(c);//线程池运行状态

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                    !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) {
                //线程池状态stop或者其他状态
                return false;
            }

            for (;;) {
                int wc = workerCountOf(c);
                //1.目前线程个数超过总容量
                //2.大于核心线程或者大于最大线程数 返回true
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //使用CAS 添加线程数
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                //重新获取状态
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        //
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //将任务封装成一个Worker
            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());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //将工作线程保存在集合中 使用hashSet保存
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            //记录最大值
                            largestPoolSize = s;
                        workerAdded = true; //添加工作线程成功
                    }
                } finally {
                    mainLock.unlock();
                }
                // 如果成功添加了 Worker,就可以启动 Worker 了
                if (workerAdded) {
                    t.start(); //启动工作线程
                    workerStarted = true; //启动工作线程成功
                }
            }
        } finally {
            if (! workerStarted) //启动失败
                addWorkerFailed(w);
        }
        return workerStarted;
    }

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;

        /**
         * 将运行任务封装成worker
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            //调用线程工作创建一个新的线程,创建的线程到时候用来执行任务
            this.thread = getThreadFactory().newThread(this);
        }

        //任务实际执行的方法
        //因为上层是通过firstTask将要执行的任务传递进来的,所以具体执行就是run 
        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }
 }

runWorker

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //执行任务的地方 如果获取不到任务就阻塞
            //getTask()方法
            while (task != null || (task = getTask()) != null) {
                w.lock(); //加锁
                //判断当前线程是否大于stop 被拒
                //
                if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) {
                    wt.interrupt();
                }
                try {
                    //执行任务前的操作
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //这里其实就是执行Worker的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 {
            //getTask返回了null,非核心的指定时间没有获取到任务超过设置的时候,woker结束
            processWorkerExit(w, completedAbruptly);
        }
    }

getTask

    //1,核心线程不会销毁,所以一直会等待获取任务
    //2.非核心超过了设置的等待时间
    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.
            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 {
                Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

小结

在这里插入图片描述

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

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

相关文章

关于前端学习的思考-word-wrap和word-break的区别

如上图word-wrap里面的break-word就是按照单词来换行的&#xff0c;空格在前&#xff0c;连字符在后的时候&#xff0c;按照连字符进行换行&#xff0c;那么空格和连字符哪一个拥有优先级呢&#xff1f; 连字符在前&#xff0c;空格在后的时候&#xff0c;还是按照连字符进行换…

分享87个节日PPT,总有一款适合您

分享87个节日PPT&#xff0c;总有一款适合您 87个节日PPT下载链接&#xff1a;https://pan.baidu.com/s/1eUxA59uQ-hZWWpFzzDuCkQ?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易…

理解SpringIOC和DI第一课(Spring的特点),IOC对应五大注解,ApplicationContext vs BeanFactory

Spring是一个包含众多工具等Ioc容器 对象这个词在Spring范围内&#xff0c;称为bean Spring两大核心思想 1.IOC (IOC是控制反转&#xff0c;意思是控制权反转-控制权&#xff08;正常是谁用这个对象&#xff0c;谁去创建&#xff0c;&#xff09;-控制对象的控制权&#xf…

中序和前/后序遍历构造二叉树———通用做法

1. 前序和中序遍历 **思路&#xff1a;我们每一次一定可以根据递归确定根节点是哪个&#xff0c;就是前序第一个数&#xff0c;然后找中序遍历这个点&#xff0c;看左子树有几个节点&#xff0c;右子树有几个节点&#xff0c;然后就可以根据节点个数&#xff0c;递归左子树和右…

分享66个焦点幻灯JS特效,总有一款适合您

分享66个焦点幻灯JS特效&#xff0c;总有一款适合您 66个焦点幻灯JS特效下载链接&#xff1a;https://pan.baidu.com/s/10bqe09IAZt_hbsZlXaxkxw?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;…

搭建若依框架完成医疗项目 ——业务流程及页面展示

目录 一、搭建若依项目 1.1 快速了解 1.1.1 技术选型 1.1.2 内置功能 1.2 环境部署 二、医疗项目业务 2.1 门诊模块 2.2 住院模块 2.3 药房药库 2.4 表设计 三、项目展示 3.1 项目背景 3.2 门诊功能模块 3.2.1 患者档案 3.2.2 门诊卡信息 ​编辑 3.2.3 患者…

QueryRunner报红处理

如图&#xff0c;有同学反映QueryRunner报红&#xff0c;就是没有导包 自己去找项目的地址&#xff0c;找到web文件夹下的WEB-INF 把这些jar包都粘贴进去&#xff0c;以后项目基本都会用到的&#xff0c;资源自己去找 粘贴好后打开文件的Project Structure 点击Dependencies 点…

探索Java中的synchronized关键字

第1章&#xff1a;引言 咱们程序员在面对多线程编程时&#xff0c;经常会听到一个词——synchronized。这个词在Java世界里就像是一把万能钥匙&#xff0c;打开并发编程的大门。但是&#xff0c;你知道吗&#xff1f;虽然synchronized用得多&#xff0c;但真正深入理解它的人并…

solidity实现ERC721代币标准发布NFT

文章目录 1、非同质化货币&#xff08;NFT&#xff09;- 维基百科2、IERC1653、IERC7214、IERC721Receiver5、IERC721Metadata6、ERC7217、ERC721 NFT 的实现8、编译部署 1、非同质化货币&#xff08;NFT&#xff09;- 维基百科 非同质化代币&#xff08;英语&#xff1a;Non-F…

【滑动窗口】水果成篮

水果成篮 904. 水果成篮 - 力扣&#xff08;LeetCode&#xff09; 文章目录 水果成篮题目描述问题转化 算法原理解法一解法二 代码编写C代码&#xff1a;使用容器数组模拟哈希表 Java代码使用容器数组模拟哈希表 题目描述 你正在探访一家农场&#xff0c;农场从左到右种植了一…

Java 数据结构篇-用链表、数组实现队列(数组实现:循环队列)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 队列的说明 1.1 队列的几种常用操作 2.0 使用链表实现队列说明 2.1 链表实现队列 2.2 链表实现队列 - 入栈操作 2.3 链表实现队列 - 出栈操作 2.4 链表实现队列 …

养身馆推拿会员管理系统,佳易王推拿会员管理软件短信设置教程

养身馆推拿会员管理系统&#xff0c;佳易王推拿会员管理软件短信设置教程 一、佳易王会员管理软件大众版 部分功能简介&#xff1a; 1、会员信息登记 &#xff1a;可以直接使用手机号登记&#xff0c;也可以使用实体卡片&#xff0c;推荐用手机号即可。 2、会员卡类型 &…

【每日一题】可获得的最大点数

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;滑动窗口方法二&#xff1a;前缀和 写在最后 Tag 【滑动窗口】【前缀和】【数组】【2023-12-03】 题目来源 1423. 可获得的最大点数 题目解读 在一排卡牌中拿出 k 张卡牌&#xff0c;每次必须从这一排卡牌的开头或者…

运维02:Linux

Linux安装 VMWare安装&#xff1a;夸克网盘分享&#xff08;提取码&#xff1a;refg&#xff09; CentOS安装&#xff1a;Index of /centos/7.9.2009/isos/x86_64/ Xshell安装&#xff1a;百度网盘 请输入提取码&#xff08;提取码&#xff1a;juau&#xff09; 环境准备 1、…

Pikachu(三)

RCE(remote command/code execute)概述 RCE漏洞&#xff0c;可以让攻击者直接向后台服务器远程注入操作系统命令或者代码&#xff0c;从而控制后台系统。 远程系统命令执行 一般出现这种漏洞&#xff0c;是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口 比如我…

Cmkae外部依赖管理

文章目录 一、cmake依赖管理介绍二、源码依管理1. FetchContent与find_package进行集成 2. CPM3. git submodule附加&#xff1a; address_sanitizer 和 undefined sanitizer 一、cmake依赖管理介绍 CMake 是跨平台的构建系统&#xff0c;支持 C/C、Objective-C、Fortran 等多种…

C++基础 -36- 模板之模板函数

模板函数格式 template <class T> void allexchange(T a,T b) {T c;c*a;*a*b;*bc; }模板函数可以增强函数的通用性 举例说明&#xff0c;使用一个模板函数实现了两个的函数的功能 #include "iostream"using namespace std;void myexchangeint(int* a,int* …

Grafana部署与Zabbix集成,搭建开源IT监控平台

Grafana部署与Zabbix集成 目前在一家公司主要是网络、运维、IT支持&#xff0c;每次需要检查服务器状态都是需要手动登录系统进行查看&#xff0c;因此想着部署一套监控系统&#xff0c;功能上需要实现监控、可视化、告警等。由于预算没有&#xff0c;服务器资源倒是有空闲的&a…

【hacker送书活动第7期】Python网络爬虫入门到实战

第7期图书推荐 内容简介作者简介大咖推荐图书目录概述参与方式 内容简介 本书介绍了Python3网络爬虫的常见技术。首先介绍了网页的基础知识&#xff0c;然后介绍了urllib、Requests请求库以及XPath、Beautiful Soup等解析库&#xff0c;接着介绍了selenium对动态网站的爬取和S…

电容和电感

一、电感 1&#xff09;图片 2&#xff09;作用 a&#xff09;储存容量 例如dcdc转换器的原理,将一个电压值转换成另外一个电压值 b&#xff09;选择信号 比如空气中弥漫着很多信号&#xff0c;我们应该怎么选取我们所需要的信号。 电感和电容可以看成一个电阻&#xff0c;当电…