线程池各参数学习

news2024/10/6 18:28:01

线程池学习_alutimo的博客-CSDN博客尚硅谷java并发包JUC线程池部分学习总结https://blog.csdn.net/qq_41228145/article/details/125650075老生常谈

线程池的参数ThreadPoolExecutor.java 的构造器

/**
 * Creates a new {@code ThreadPoolExecutor} with the given initial
 * parameters.
 *
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @param maximumPoolSize the maximum number of threads to allow in the
 *        pool
 * @param 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.
 * @param unit the time unit for the {@code keepAliveTime} argument
 * @param 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.
 * @param threadFactory the factory to use when the executor
 *        creates a new thread
 * @param handler the handler to use when execution is blocked
 *        because the thread bounds and queue capacities are reached
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue}
 *         or {@code threadFactory} or {@code handler} is null
 */
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;
}

corePoolSize : 核心池的大小,一直再线程池里,即使没活干。

在创建线程池后,默认情况下线程池中没有一个线程,而是等待任务来后,才会创建线程去执行任务。除非调用了 prestartCoreThread()/prestartAllCoreThreads()

即预创建线程(创建所有corepollsize个线程或者一个线程)。当线程数达到了corepoolsize再有任务过来时,就会把任务放入缓存队列中。

核心线程数会被回收吗?需要什么设置?

默认是不会被回收的。但是可以通过下面的方法进行修改。allowCoreThreadTimeOut()

maximumPoolSize:线程池中的最大线程数。

keepAliveTime如果当前线程池中的线程数超过了corePoolSize,那么如果在keepAliveTime时间内都没有新的任务需要处理,那么超过corePoolSize的这部分线程就会被销毁。默认情况下是不会回收core线程的,可以通过设置allowCoreThreadTimeOut改变这一行为。

Unit:表示KeepAliveTime的时间单位,有7种取值。

workQueue:一个阻塞队列,用来存储等待执行的任务(任务缓存队列)。一般的阻塞队列会有以下几种选择:

ArrayBlockingQueue,需要指定大小 还是指定公平策略

LinkedBlockingQueue,-- 注意这个队列默认大小是 Integer.MAX_VALUE,所以尽量指定

SynchronousQueue

三种形式一般采用 LinkedBlockingQueue 和 SynchronousQueue。

ThreadFactory: 线程工厂,主要用来创建线程。

Handler:表示拒绝处理任务的策略。主要是当线程数量达到了上线,同时任务已经达到了队列的边界也就是上限

线程池的参数ThreadPoolExecutor.java 的构造器的 execute执行方法

   public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:  --这里直接就告诉你了运行任务分三步来
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
            如果当前运行的线程数小于指定核心线程数量,来一个task就创建一个核心线程数
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
           如果
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        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);
    }

生活中的例子。比如超市购物

超市今天所有商品打五折,我就拿了一瓶水想去结账,此时超市只有一个收银小妹,结果排在我前面的家伙拿了满满两大车商品结账,由于周围大爷大妈全来买东西,排队也排的贼长,都排到购物区了

我怎么办呢?我就一瓶水啊,我不太想等,此时我有几个选择,

1.跟前面的家伙说,我就一瓶水,让我排你前面去吧

2.我认识超市老板,要老板再开两个结账窗口

3.老老实实排队

假设 我选择了1 会有什么后果,1.别人不同意,给我两个大耳刮子 2我后面的家伙也只拿了一包辣条,看到我插队了,也想插队,后面拿了一包薯片的也想插队

我选择了2,开了两个新结账窗口,因为马上就到我前面的了,他留在原地,我跑到新窗口了,

但是此时排队人太多了,怎么办

1.关闭超市入口

2.限制排队人数或者不管 或者让排在后面的滚蛋

3.开通同城派送服务,你网上下单,购物直接留下

到了网上十一点顾客也差不多买完了,没什么人了,新开的窗口也关闭了,只有最开始的小妹了。

这个故事中

核心线程=最开始的收银小妹

最大线程数=能开的所有结账窗口

队列=限制排队人数   我插队=公平锁 非公平锁

保留时间=十一点了 其他临时窗口关闭了

oom=关闭超市入口

拒绝策略=让排队后面的滚蛋, 不允许排队 和同城派送

这个很多地方不太准确,只是为了让大家理解下

在这里插入图片描述

借用图片来说,

1.提交任务 先看核心线程数是否已经满了。

        没有满就创建核心线程或者复用核心线程数量->执行任务

        满了,就去检查队列还能不能放

2. 检查队列还不能放,有的队列无界 随便放,有的队列有界

        能放进去,就把任务放进去,等着前面核心线程完成了再完成队列里的任务

        满了放不进去,就去创建非核心线程

3.核心线程全在干活,队列又排满了,活还在不断的发放,创建非核心线程,让非核心线程去干活

        非核心线程数量没有满,就开始创建非核心线程干活

         活实在是太多了,非核心线程也满了,就开始使用拒绝策略。

4.拒绝策略。

这里有个关键点注意了,就是第2和第3的顺序。

假设任务一个个来,task1 task2 task3...task100 任务的执行顺序不一定是 1 2 3 4 5 6这样的!!!

下面开始进行实验。

一个线程工厂 主要是每个线程命名

一个线程任务  模拟每个线程工作 每个任务工作1s

class MyThreadFactory implements ThreadFactory {
    private final String name = "thread-cclovezbf-%s";
    private final AtomicInteger nextId = new AtomicInteger(1);
    // 定义线程组名称,在利用 jstack 来排查问题时,非常有帮助
    public MyThreadFactory(String whatFeatureOfGroup) {
    }
    @Override
    public Thread newThread(Runnable task) {
        String threadName = String.format(name, nextId.toString());
        System.out.println("线程" + threadName + "已经被创建--");
        Thread thread = new Thread(task);
        thread.setName(threadName);
        nextId.addAndGet(1);
        return thread;
    }

}

class Task extends Thread {
    private final int num;
    public Task(int num) {
        this.num = num;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " task" + num + "--start--");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " task" + num + "--end--");
    }
}

测试1 任务被执行的顺序

@Test
public void testCorePoolSize() throws InterruptedException {

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        2,
        4,
        20, TimeUnit.SECONDS,
        new ArrayBlockingQueue<Runnable>(2),
        new MyThreadFactory("cclovezbf")
        //注意我这里核心2 最大是4 
);
    for (int i = 1; i < 6; i++) {
        System.out.println("开始提交任务"+i);
        threadPoolExecutor.execute(new Task(i));
        Thread.sleep(1);--
    }
    threadPoolExecutor.shutdown();
    Thread.sleep(300000);
}

 

提交任务1和2 都创建了线程 ,此时就是核心线程 cclovezbf1 cclovezbf2

提交任务3和4 都是放到队列里了 我设置的队列长度2 此时任务3和任务4 等待被执行

提交任务5,此时队列已满,开始创建非核心线程cclovezbf3

然后线程cclovezbf1 cclovezbf2干完了任务1和任务2,开始从队列找活 找到了任务3和任务4

和上述图顺序一致。

测试2 非核心线程回收

 @Test
    public void testKeepAlive() throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                1,
                3,
                5, TimeUnit.SECONDS, //只保留5s
                new ArrayBlockingQueue<Runnable>(1),
                new MyThreadFactory("cclovezbf")
        );
        for (int i = 1; i < 5; i++) {
            System.out.println("开始提交任务"+i);
            threadPoolExecutor.execute(new Task(i));
            Thread.sleep(1); //这里sleep 1 是为了让打印的更加直观
        }
        Thread.sleep(6000);
        System.out.println("=====休息一会=====");
        for (int i = 5; i < 9; i++) {
            System.out.println("开始提交任务"+i);
            threadPoolExecutor.execute(new Task(i));
            Thread.sleep(1); //这里sleep 1 是为了让打印的更加直观
        }
        Thread.sleep(20000);
        threadPoolExecutor.shutdown();
    }

 可以看到在sleep6s后,再重新提交任务的时候 cclovezbf1还在,cclovezbf2和3已经不在了,取而代之的是重新创建了cc4。

测试3 队列

队列分为三种

ArrayBlockingQueue,      需要指定大小 还是指定公平策略

LinkedBlockingQueue,-- 注意这个队列默认大小是 Integer.MAX_VALUE,所以尽量指定

SynchronousQueue

首先我们要清楚公平和非公平的策略。

public class FairLockDemo {
   static ReentrantLock noFairLock = new ReentrantLock(false);//默认 非公平锁
   static ReentrantLock fairLock = new ReentrantLock(true); //公平锁

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========公平锁===========");
        testLock(fairLock);
        Thread.sleep(1000);
        System.out.println("========非公平锁===========");
        testLock(noFairLock);
    }

    private static void testLock(ReentrantLock lock){
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                for (int j = 0; j < 2; j++) {
                    lock.lock();
                    System.out.println("当前线程:" + Thread.currentThread().getName()+"获取锁");
                    try {
                        Thread.sleep(1);  
                //这里我故意sleep了1ms 是为了防止 线程1都执行第二次for循环了 线程2和3还没启动
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.unlock();
                }
            }).start();
        }
    }
}

参考文章面试突击:公平锁和非公平锁有什么区别?-公平锁和非公平锁区别

这里举个例子,高速路上单行道,1 2 3 开车去从深圳到湖南

公平锁 简单的来说就是排队,在深圳的时候1在前2在中3在后,那么在湖南的时候还是这样,

非公平锁就是有人插队,1时速开80,这谁受得了啊,2很生气直接应急车道超车超到1前面去了,

为什么有这两种锁?个人理解

公平锁 保证了公平,先来先到,比如123 按顺序开车迟早都会开到湖南的,没有问题

非公平锁保证了效率,比如2 3 应急车道超车,后面的4 5 6 7 8....100w都超了1的车,这1啥时候才能开到湖南去啊,但是这种情况就大大的提高了通行效率。

使用场景,比如我们向线程池提交很多任务,

我们希望任务尽量按照先提交先完成的顺序来,比如页面点击一下就是提交一个任务,有的提交了任务肯定是希望尽可能快的获取结果,这种时候用公平锁,(虽然这样会使得完成时间平均增加一点,比如10个任务 每个增加1s 大家也不会有意见),如果使用非公平锁,有的人提交的任务抢不到线程工作,直接增加了10s,那不得贼气。

公平锁执行流程

获取锁时,先将线程自己添加到等待队列的队尾并休眠,当某线程用完锁之后,会去唤醒等待队列中队首的线程尝试去获取锁,锁的使用顺序也就是队列中的先后顺序,在整个过程中,线程会从运行状态切换到休眠状态,再从休眠状态恢复成运行状态,但线程每次休眠和恢复都需要从用户态转换成内核态,而这个状态的转换是比较慢的,所以公平锁的执行速度会比较慢。

非公平锁执行流程

当线程获取锁时,会先通过 CAS 尝试获取锁,如果获取成功就直接拥有锁,如果获取锁失败才会进入等待队列,等待下次尝试获取锁。这样做的好处是,获取锁不用遵循先到先得的规则,从而避免了线程休眠和恢复的操作,这样就加速了程序的执行效率。

还有一个就是队列长度,队列长度过长,等待任务过多会导致oom就不说了。

测试 拒绝策略

    private void testRejectHandler(RejectedExecutionHandler policy) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                1,
                1,
                5, TimeUnit.SECONDS, //只保留5s
                new ArrayBlockingQueue<Runnable>(1,false),
                new MyThreadFactory("cclovezbf")
        );
        threadPoolExecutor.setRejectedExecutionHandler(policy);
        for (int i = 0; i < 3; i++) {
            threadPoolExecutor.execute(new Task(i));
        }
        threadPoolExecutor.shutdown();
        Thread.sleep(10000);
    }

    @Test
    public void testCallerRunsPolicyy() throws InterruptedException {
        ThreadPoolExecutor.CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();
        testRejectHandler(callerRunsPolicy);
    }

    @Test
    public void testAbortPolicy() throws InterruptedException {
        ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();
        testRejectHandler(abortPolicy);
    }

    @Test
    public void testDiscardPolicy() throws InterruptedException {
        ThreadPoolExecutor.DiscardPolicy discardPolicy = new ThreadPoolExecutor.DiscardPolicy();
        testRejectHandler(discardPolicy);
    }

    // 8号任务直接不见了
    @Test
    public void testDiscardOldestPolicy() throws InterruptedException {
        ThreadPoolExecutor.DiscardOldestPolicy discardOldestPolicy = new ThreadPoolExecutor.DiscardOldestPolicy();
        testRejectHandler(discardOldestPolicy);
    }

 //当触发拒绝策略时,如果线程池未关闭,则直接使用调用者线程,执行任务
ThreadPoolExecutor.CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();
//丢弃任务,并抛出异常信息。必须处理好异常,否则会打断当前执行的流程
ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();
//从源码中应该能看出来,此拒绝策略是对于当前任务不做任何操作,简单言之:直接丢弃。
ThreadPoolExecutor.DiscardPolicy discardPolicy = new ThreadPoolExecutor.DiscardPolicy();
//当触发拒绝策略时,如果线程池未关闭,则丢弃阻塞队列中最老的一个任务,并将新任务加入.
ThreadPoolExecutor.DiscardOldestPolicy discardOldestPolicy = new ThreadPoolExecutor.DiscardOldestPolicy();

核心线程=最大线程=队列=1 所以能接受2个任务,我提交了4个任务

callerRunsPolicy,直接开启主线程和核心线程去处理任务,多余的任务继续运行

 abortPolicy 核心线程满,队列满,直接抛异常

  discardPolicy 多余的任务直接丢了,不管了 假装没看到。

discardOldestPolicy,丢最老的任务 处理最新的任务。 上面的丢最新的任务,处理先来的任务

测试 线程创建多少合理

 https://www.cnblogs.com/MrLiuZF/p/15188349.html 这篇文章有理有据。

  IO密集型=2Ncpu(可以测试后自己控制大小,2Ncpu一般没问题)(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)

  计算密集型=Ncpu(常出现于线程中:复杂算法)

线程池如何确定线程数量  这篇文章好像是搜索的主流答案

在高并发的情况下采用线程池,有效的降低了线程创建释放的时间花销及资源开销,如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。(在JVM中采用的处理机制为时间片轮转,减少了线程间的相互切换)

那么在高并发的情况下,我们怎么选择最优的线程数量呢?选择原则又是什么呢?这个问题去哪网的技术总监问过我,这里总结一下。

如果是CPU密集型应用,则线程池大小设置为N+1

对于计算密集型的任务,在拥有N个处理器的系统上,当线程池的大小为N+1时,通常能实现最优的效率。(即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保CPU的时钟周期不会被浪费。- 摘自《Java Concurrency In Practise》

如果是IO密集型应用,则线程池大小设置为2N+1。

任务一般可分为:CPU密集型IO密集型混合型,对于不同类型的任务需要分配不同大小的线程池。

CPU密集型任务 尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。

IO密集型任务 可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。

混合型任务可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。 因为如果划分之后两个任务执行时间相差甚远,那么先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。

其实这个用自己做例子来说。你觉得我们能够一心几用?

我们就一个大脑,

io密集任务,可以看作不怎么用大脑的任务,只需要又简单的回应的

如果让你当保安,你是不是可以一下可以看七八个监控画面,

让你和美女聊微信,是不是可以一下和七八个同时聊天

cpu密集任务,可以看做要脑子的任务,

让你写高考题,你能最后一个大题 一个点,这样轮流来么,

让你打游戏,你能同时打王者和lol么。

甚至如果你又平板或者电脑分屏,你看你能不能同时看两篇小说,我试过 脑子根本转不过来,

所以像这种复杂任务还是一个线程干一个活最好。

 ----------------------------------------------------------------------------------------------------------------------

最后来学习下源码

    /**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@code RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        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);
    }

         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
 如果正在运行的核心线程数小于指定的核心线程数,让接到任务时候,开启一个新的核心线程让它干活
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *

如果队列里面能够塞下,就塞
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.

如果队列里面塞不下 就开新线程 非核心线程。

 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); 

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

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

相关文章

PLC【西门子】几种常见的连接口和通讯协议简介

S7-200 PLC支持的几种通讯协议 一、PPI通讯 是西门子公司专为s7-200系列plc开发的通讯协议。内置于s7-200CPU中。PPI协议物理上基于RS-485口,通过屏蔽双绞线就可以实现PPI通讯。PPI协议是一种主-从协议。主站设备发送要求到从站设备,从站设备响应,从站不能主动发出信息。主…

简述什么是微前端 微前端几种框架的区别

微前端就是将各个模块分成不同项目 方便多个团队一起开发互相不影响 例如&#xff1a;a团队维护较老的项目使用angular&#xff0c;b团队开发react&#xff0c;c团队开发vue 。按道理说abc三个项目并没有关联&#xff0c;但是他们又都是公司内部管理的系统。需要集成在一起 &…

智能排班系统 【管理系统功能、操作说明——中篇】

文章目录 页面与功能展示企业管理角色管理用户管理系统管理员身份使用企业管理员身份使用门店管理员身份使用 门店管理职位管理排班规则设置节日管理消息管理 页面与功能展示 企业管理 企业管理页面如图 34所示&#xff0c;在企业管理页面&#xff0c;系统管理员可以查询所注…

IAT Hook

一、IAT HOOK介绍 IAT (Import Address Table) HOOK 是一种在 Windows 程序中进行函数钩子的技术。它通过修改程序的导入地址表来实现对目标函数的替换或拦截。 在 Windows 运行时&#xff0c;程序需要调用其他模块&#xff08;DLL&#xff09;中的函数来完成特定的功能。为了…

java项目打包方式

普通项目打包 项目内容很简单&#xff0c;只是引用了一个三方包。 打包步骤 File-Project Structure... 点击确定后选择Build - Build Artifacts.. 选择build即可&#xff0c;可以查看编译日志 maven项目打包 若果是普通项目就先转为maven项目。 右键项目选择第二项add frame…

【Netty】Netty 解码器(十二)

文章目录 前言一、编解码概述1.1、编解码器概述1.2、Netty 内嵌的编码器 二、解码器2.1、ByteToMessageDecoder 抽象类2.1.1、常用方法2.1.2、将字节转为整形的解码器示例 三、ReplayingDecoder 抽象类四、MessageToMessageDecoder 抽象类总结 前言 回顾Netty系列文章&#xf…

K-Means算法实现鸢尾花数据集聚类

目录 1. 作者介绍2. K-Means聚类算法2.1 基本概念2.2 算法流程 3. K-Means聚类算法实现3.1 鸢尾花数据集3.2 准备工作3.3 代码实现3.4 结果展示 4. 问题与解析参考链接 1. 作者介绍 张勇&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向…

第3章“程序的机器级表示”:算术和逻辑操作

文章目录 3.5 算术和逻辑操作3.5.1 加载有效地址3.5.2 一元和二元操作3.5.3 移位操作3.5.4 讨论3.5.5 特殊的算术操作 3.5 算术和逻辑操作 下图列出了一些双字整数操作&#xff0c;分为四类。 二元操作有两个操作数&#xff0c;而一元操作只有一个操作数。 描述这些操作数的…

极光笔记 | EngageLab Push的多时区解决方案

01、引言 多时区问题一直是全球客户和终端用户面临的挑战之一。EngageLab Push 致力于解决这个问题&#xff0c;确保全球各地的终端用户可以平等地享受到同样的推送服务&#xff0c;同时让客户能够更好地管理不同时区的应用和对应的终端用户。 02、解决多时区问题的总体方案 1…

软件测试----软件开发模型

1、瀑布模型 &#xff08;1&#xff09;瀑布模型如下 &#xff08;2&#xff09;瀑布模型的缺点&#xff1a; 在瀑布模型中&#xff0c;测试是在编码结束后才介入&#xff0c;对软件开发流程前期质量是没有保障的 &#xff08;3&#xff09;采用瀑布模型的场景&#xff1a; …

31 KVM管理系统资源-管理虚拟内存NUMA

文章目录 31 KVM管理系统资源-管理虚拟内存NUMA31.1 NUMA简介31.2 配置Host-NUMA操作步骤 31.3 配置Guest-NUMA操作步骤 31 KVM管理系统资源-管理虚拟内存NUMA 31.1 NUMA简介 传统的多核运算使用SMP&#xff08;Symmetric Multi-Processor&#xff09;模式&#xff1a;将多个…

Flume系列:Flume 自定义Interceptor拦截器

目录 Apache Hadoop生态-目录汇总-持续更新 1&#xff1a;Interceptor拦截器的使用场景 2&#xff1a;Interceptor拦截器在采集流程中的位置 3&#xff1a;自定义Interceptor拦截器 pom.xml 拦截器java代码 打包上传 4&#xff1a;使用自定义的拦截器 方式一&#xff1…

Springboot +spring security,自定义认证和授权异常处理器

一.简介 在Spring Security中异常分为两种&#xff1a; AuthenticationException 认证异常AccessDeniedException 权限异常 我们先给大家演示下如何自定义异常处理器&#xff0c;然后再结合源码帮助大家进行分析 二.创建项目 如何创建一个SpringSecurity项目&#xff0c;前…

分布式锁和事务关系的细节

使用redssion在redis上以及结合自定义注解利用spring的环绕切面来实现分布式锁功能 代码示例 controller、service层 RequestMapping("insertNumber/{number}/{id}") public boolean insertNumber(PathVariable Long number,PathVariable Long id){return testSer…

rust 中protobuf生成与使用

首先创建一个项目proto 进入到这个文件夹中 创建我们的proto文件 初始化的项目结构是这个样子的 新建一个hello.proto文件内容如下 syntax "proto3";package hello;service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {} }message HelloRequest …

干货 | 师兄手把手教你如何踏上科研道路

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是喵君姐姐&#xff5e; 今天&#xff0c;邀请到鲁小白&#xff0c;给大家分享一下他踏上科研道路的心路历程。 大家好&#xff0c;我是鲁小白&#xff0c;我真正进入科研的时间&#xff0c;研究生3年再…

【C++】类和对象——类的引入、类的访问限定符、类的作用域、类的实例化、类的储存、this指针的引出和特性

文章目录 1.类的引入2.类的访问限定符3.类的作用域4.类的实例化5.类的储存6.this指针6.1this指针的引出6.2this指针的特性 1.类的引入 C是在C的基础上加以扩展。 在C语言中&#xff0c;我们想要让一个类型含有多种成员变量&#xff0c;我们使用结构体&#xff1b;而在C中我们可…

Doris节点扩容及数据表

扩容和缩容 上篇文章简单讲了doris的安装&#xff0c;本章分享的是doris中fe和be节点的扩容缩容以及doris的数据表1、FE 扩容和缩容 使用 MySQL 登录客户端后&#xff0c;可以使用 sql 命令查看 FE 状态&#xff0c;目前就一台 FE mysql -h linux -P 9030 -uroot -p mysql&…

python+django乡村居民数据的可视化平台

本论文主要论述了如何使用Django框架开发一个乡村振兴数据的可视化平台 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述乡村振兴数据的可视化平台的当前背景以…

拼多多二面,原来是我对自动化测试的理解太浅了

如果你入职一家新的公司&#xff0c;领导让你开展自动化测试&#xff0c;作为一个新人&#xff0c;你肯定会手忙脚乱&#xff0c;你会如何落地自动化测试呢&#xff1f; 01 什么是自动化 有很多人做了很长时间的自动化但却连自动化的概念都不清楚&#xff0c;这样的人也是很悲…