Java线程池源码分析

news2024/9/25 13:18:34

Java 线程池的使用,是面试必问的。下面我们来从使用到源码整理一下。

1、构造线程池

  • 通过Executors来构造线程池
1、构造一个固定线程数目的线程池,配置的corePoolSize与maximumPoolSize大小相同,
同时使用了一个无界LinkedBlockingQueue存放阻塞任务,因此多余的任务将存在阻塞队列,
不会由RejectedExecutionHandler处理 
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }


2、构造一个缓冲功能的线程池,配置corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,
keepAliveTime=60s,以及一个无容量的阻塞队列 SynchronousQueue,因此任务提交之后,
将会创建新的线程执行;线程空闲超过60s将会销毁 
 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }


3、构造一个只支持一个线程的线程池,配置corePoolSize=maximumPoolSize=1,
无界阻塞队列LinkedBlockingQueue;保证任务由一个线程串行执行 
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

4、构造有定时/延时功能的线程池,配置corePoolSize,无界延迟阻塞队列DelayedWorkQueue;
有意思的是:maximumPoolSize=Integer.MAX_VALUE,由于DelayedWorkQueue是无界队列,
所以这个值是没有意义的
对于一些不能及时处理,需要延时处理的操作,用ScheduledExecutorService处理很方便,
比如我们在某些条件下需要清理redis/mysql中数据时,但是可能当前有些地方还需要用到(并发),这时用ScheduledExecutorService处理非常合适,
虽然也可以用定时任务处理,但是定时任务会一直执行,而这里的场景是满足一定条件去执行,而执行的机会又很少。
 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

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

public ScheduledThreadPoolExecutor(int corePoolSize,
                             ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

注:阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,
或者当队列是满时,往队列里添加元素的操作会被阻塞。
试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。
同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,
如从队列中移除一个或者多个元素,或者完全清空队列.
  • 通过ThreadPoolExecutor自定义线程池
public ThreadPoolExecutor(int corePoolSize, 
                              int maximumPoolSize,  
                              long keepAliveTime,
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue, 
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler )
* corePoolSize 核心线程池大小----1
* maximumPoolSize 最大线程池大小----3
* keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间----30
* keepAliveTime时间单位----TimeUnit.MINUTES 
* workQueue 阻塞队列----new ArrayBlockingQueue<Runnable>(5)----阻塞队列的容量是5
* threadFactory 新建线程工厂----new CustomThreadFactory()----定制的线程工厂 
* rejectedExecutionHandler 当提交任务数超过maxmumPoolSize+workQueue之和(3+5),
      即当提交第9个任务时(前面线程都没有执行完,此测试方法中用 sleep(30), 
      任务会交给RejectedExecutionHandler来处理 

new ThreadPoolExecutor(  
                1,  
                3,  
                30,  
                TimeUnit.MINUTES,  
                new ArrayBlockingQueue<Runnable>(5),  
                new CustomThreadFactory(),  
                new CustomRejectedExecutionHandler());  

package com.vendor.control.web.device;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by zhangkai on 2019/8/12.
 */
public class CustomThreadPoolExecutor
{
    private ThreadPoolExecutor pool = null;
    
    public void init() {
        pool = new ThreadPoolExecutor(
                1,
                3,
                30,
                TimeUnit.MINUTES,
                new ArrayBlockingQueue<Runnable>(5),
                new CustomThreadFactory(),
                new CustomRejectedExecutionHandler());
    }


    public void destory() {
        if(pool != null) {
            pool.shutdownNow();
        }
    }


    public ExecutorService getCustomThreadPoolExecutor() {
        return this.pool;
    }

    private class CustomThreadFactory implements ThreadFactory
    {

        private AtomicInteger count = new AtomicInteger(0);

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            String threadName = CustomThreadPoolExecutor.class.getSimpleName() + count.addAndGet(1);
            System.out.println(threadName);
            t.setName(threadName);
            return t;
        }
    }


    private class CustomRejectedExecutionHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            try {
                // 当使用blockingqueue的offer插入数据时,如果队列已满,那么阻塞指定时间等待队列可用,
//等待期间如果被中断,那么抛出InterruptedException。
                // 如果插入成功,那么返回true,如果在达到指定时间后仍然队列不可用,
//那么返回false。===========超时退出
                // 使用put 时,插入数据时,如果队列已满,那么阻塞等待队列可用,等待期间如果被中断,
//那么抛出InterruptedException。 =============  一直阻塞:
                System.out.println("拒绝任务");
                executor.getQueue().offer(r); //会有任务线程不执行
                //executor.getQueue().put(r); //不会有任务线程不执行
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }



    // 测试构造的线程池
    public static void main(String[] args) {

        CustomThreadPoolExecutor exec = new CustomThreadPoolExecutor();
        // 1.初始化
        exec.init();

        ExecutorService pool = exec.getCustomThreadPoolExecutor();
        for(int i=1; i<=10; i++) {
            System.out.println("提交第" + i + "个任务!");
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(30);
                        System.out.println(">>>task is running=====");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }


        // 2.销毁----此处不能销毁,因为任务没有提交执行完,如果销毁线程池,任务也就无法执行了
        // exec.destory();

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2、线程池执行流程

源码

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 1、工作线程 < 核心线程 
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 2、运行态,并尝试将任务加入队列;如果能加入,说明队列没满
        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);
        } // 3、工作线程 < 核心线程,并且队列满了,那么继续新建线程,尝试使用最大线程运行
        else if (!addWorker(command, false))
            reject(command);
    }

从上面这个图中可以看出,在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,并且工作线程数<核心线程数时,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
在核心线程数创建以后,就不会再关闭了,这个创建过程类似懒加载,只有需要用的时候才去创建

当核心线程数执行完其第一个任务以后,就会阻塞,等待从队列中获取任务(getTask),获取到的话,线程就继续执行任务。见下面runWorker源码。

ThreadPoolExecutor执行顺序总结:
当线程数小于核心线程数时,创建线程。
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
当线程数大于等于核心线程数,且任务队列已满
若线程数小于最大线程数,创建线程
若线程数等于最大线程数,抛出异常,拒绝任务

简单的说,

  • addWorker(command, true): 创建核心线程执行任务;
  • addWorker(command, false):创建非核心线程执行任务;
  • addWorker(null, false): 创建非核心线程,当前任务为空;
  • addWorker(null,true) : 预先创建corePoolSize个线程;

addWorker源码

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        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()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                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 {
            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();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //启动线程,执行任务,这里调用的其实是worker的run方法,见下面Worker构造方法
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }


//Worker构造方法,thread变量构造的线程是它本身,即当调用Worker中thread.start()时,
//最终执行的是Worker类的run方法
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }


//执行任务时,最后调用的方法
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //第一次执行task时,task肯定不为空,当firstTask执行完以后,while循环等待,
            //指导从队列中获取到task,即getTask()不为空时,getTask就是从队列中获取任务
            while (task != null || (task = getTask()) != null) {
                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())
                    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;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

3、拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

线程池的默认拒绝策略为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常。

4、线程池优雅关闭

从源码中可以看到,有两种关闭方式,shutdown和shutdownNow。

  • executorService.shutdown():线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行。
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //设置线程池状态为SHUTDOWN,之后就不能再向线程池提交任务了
            advanceRunState(SHUTDOWN);
            //遍历所有未执行任务的线程,对其设置中断
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
       //判断所有任务是否都已退出,如果退出则设置标记 “TERMINATED”
      //在这里会死循环一直等到所有线程都执行完任务后,再次中断线程
        tryTerminate();
    }

private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
//中断时,先获取锁,runWorker中执行任务时,会先lock加锁(见上面runWorker源码)
//所以,这里其实只会对中断空闲线程
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
            }
        } finally {
            mainLock.unlock();
        }
    }

从上面的源码中可以看出,当我们调用线程池的shuwdown方法时,如果线程正在执行线程池里的任务,即便任务处于阻塞状态,线程也不会被中断,而是继续执行(因为有加锁,所以interruptIdleWorkers中worker获取不到锁,所以执行不了中断)。
如果线程池阻塞等待从队列里读取任务getTask(),则会被唤醒,但是会继续判断队列是否为空,如果不为空会继续从队列里读取任务,为空则线程退出。

  • executorService.shutdownNow():线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行。
  public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
//修改线程池的状态为STOP状态
            advanceRunState(STOP);
//遍历线程池里的所有工作线程,然后调用线程的interrupt方法
            interruptWorkers();
//将队列里还没有执行的任务放到列表里,返回给调用方
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }


    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                //直接中断
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

    private List<Runnable> drainQueue() {
        BlockingQueue<Runnable> q = workQueue;
        ArrayList<Runnable> taskList = new ArrayList<Runnable>();
        q.drainTo(taskList);
        if (!q.isEmpty()) {
            for (Runnable r : q.toArray(new Runnable[0])) {
//移除工作队列中任务,同时把其返回给调用方
                if (q.remove(r))
                    taskList.add(r);
            }
        }
        return taskList;
    }

当我们调用线程池的shutdownNow时,如果线程正在getTask方法中执行,则会通过for循环进入到if语句,于是getTask返回null,从而线程退出(getTask源码中会先判断线程状态,而上一步已经把线程状态修改为STOP了)。不管线程池里是否有未完成的任务。

如果线程因为执行提交到线程池里的任务而处于阻塞状态,则会导致报错(如果任务里没有捕获InterruptedException异常),否则线程会执行完当前任务,然后通过getTask方法返回为null来退出。

总结:调用完shutdownNow和shuwdown方法后,并不代表线程池已经完成关闭操作,它只是异步的通知线程池进行关闭处理。如果要同步等待线程池彻底关闭后才继续往下执行,需要调用awaitTermination方法进行同步等待。

5、ThreadPoolExecutor参数设置

5.1 默认值

- corePoolSize=1
- queueCapacity=Integer.MAX_VALUE
- maxPoolSize=Integer.MAX_VALUE
- keepAliveTime=60s
- allowCoreThreadTimeout=false
- rejectedExecutionHandler=AbortPolicy()

5.2 自定义线程池参数的合理设置

为了说明合理设置的条件,我们首先确定有以下⼏个相关参数:

  • 1.tasks,程序每秒需要处理的最⼤任务数量(假设系统每秒任务数为100~1000)
  • 2.tasktime,单线程处理⼀个任务所需要的时间(每个任务耗时0.1秒)
  • 3.responsetime,系统允许任务最⼤的响应时间(每个任务的响应时间不得超过2秒)

corePoolSize:核心线程数

每个任务需要tasktime秒处理,则每个线程每秒可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即tasks*tasktime个线程数。
假设系统每秒任务数为100到1000之间,每个任务耗时0.1秒,则需要100x0.1⾄1000x0.1,即10到100个线程。

那么corePoolSize应该设置为大于10。具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下任务数小于200,最多时为1000,则corePoolSize可设置为20。

queueCapacity:任务队列的长度:

任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime) * responsetime=(20/0.1) * 2=400,即队列长度可设置为400。

如果队列长度设置过⼤,会导致任务响应时间过长,如以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。

maxPoolSize:最大线程数

当系统负载达到最⼤值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。
每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity) * 0.1,即60个线程,可将maxPoolSize设置为60。

keepAliveTime:

线程数量只增加不减少也不⾏。当负载降低时,可减少线程数量,如果⼀个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。keepAliveTiime设定值可根据任务峰值持续时间来设定。

rejectedExecutionHandler:

根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理

以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能⼒,应设法降低每个任务的处理时间(tasktime)。

补充:线程中断

在程序中,我们是不能随便中断一个线程的,因为这是极其不安全的操作,我们无法知道这个线程正运行在什么状态,它可能持有某把锁,强行中断可能导致锁不能释放的问题;或者线程可能在操作数据库,强行中断导致数据不一致混乱的问题。正因此,JAVA里将Thread的stop方法设置为过时,以禁止大家使用。

一个线程什么时候可以退出呢?当然只有线程自己才能知道。

所以我们这里要说的Thread的interrrupt方法,本质不是用来中断一个线程。是将线程设置一个中断状态。

当我们调用线程的interrupt方法,它有两个作用:

  • 1、如果此线程处于阻塞状态(比如调用了wait方法,io等待),则会立马退出阻塞,并抛出InterruptedException异常,线程就可以通过捕获InterruptedException来做一定的处理,然后让线程退出。
  • 2、如果此线程正处于运行之中,则线程不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以线程要在适当的位置通过调用isInterrupted方法来查看自己是否被中断,并做退出操作。

如果线程的interrupt方法先被调用,然后线程调用阻塞方法进入阻塞状态,InterruptedException异常依旧会抛出。
如果线程捕获InterruptedException异常后,继续调用阻塞方法,将不再触发InterruptedException异常。

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

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

相关文章

《PyTorch深度学习实践10》——循环神经网络-基础篇(Basic-Recurrent Neural Network)

目录一、RNN简介二、RNN Cell用法三、RNN用法三、实例&#xff1a;hello换序1.RNN Cell2.RNN四、Embedding一、RNN简介 RNN网络最大的特点就是可以处理序列特征&#xff0c;就是我们的一组动态特征。比如&#xff0c;我们可以通过将前三天每天的特征&#xff08;是否下雨&#…

七、SpringBoot_自动装配

自动装配 官方文档 SpringBoot自动配置尝试根据您添加的 jar 依赖项自动配置您的 Spring 应用程序 Spring Boot auto-configuration attempts to automatically configure your Spring application based on the jar dependencies that you have added. SpringBoot定义了一套接…

详谈IIC

前言 在嵌入式底层系统中&#xff0c;常见的通讯方式&#xff0c;串口&#xff0c;IIC&#xff0c;SPI&#xff0c;IIS等&#xff0c;一般IIC,SPI,IIS更多的采取IO模拟&#xff0c;其余CAN,UART均是硬件设计直接支持&#xff0c;而IIC主要用于多数传感器数据的读写&#xff0c…

【c++基础】

C基础入门统一初始化输入输出输入输出符输入字符串const与指针c和c中const的区别const与指针的关系常变量与指针同类型指针赋值的兼容规则引用引用的特点const引用作为形参替换指针其他引用形式引用和指针的区别inline函数缺省参数函数重载判断函数重载的规则名字粉碎C编译时函…

Goby 征文大擂台,超值盲盒等你来!

001 Goby 技术征文正式启动 Goby 致力于做最好的网络安全工具。为了促进师傅们知识共享和技术交流&#xff0c;现发起关于 Goby 的技术文章征集活动&#xff01; 欢迎所有师傅们参加&#xff0c;分享您的使用经验或挖洞窍门等&#xff0c;帮助其他人更好地了解和利用 Goby。 …

Winform界面实现控件中英文语言切换

一、业务需求 在Winform项目的开发过程中,涉及到一个基础的功能就是需要对界面中的显示语言内容可以进行选择切换,方便不同地区的使用者快速上手使用;效果如下: 二、需求分析 需要实现对Winform项目界面显示语言可选择切换步骤如下: ①修改控件的显示内容; ②获取到界面显…

【计算机网络】数据链路层

概述 封装成帧 差错检验 可靠传输 实现机制 可靠传输的实现机制 停止等待协议 回退N帧协议 选择重传协议 【计算机网络】MAC帧和PPP帧&#xff08;定义使用范围区别共同点&#xff09;_GPNU_Log的博客-CSDN博客_ppp帧 PPP帧和以太网帧 | Mixoo 数据链路层的协议有PPP协…

Rman单实例迁移到单实例

关于同平台同版本数据库之间的迁移操作的实验 ---Source DB[rootoracle-db-19cs ~]# cat /etc/redhat-release CentOS Stream release 8 [rootoracle-db-19cs ~]# --- Target DB[rootoracle-db-19ct ~]# cat /etc/redhat-release CentOS Stream release 8 [rootoracle-db-19ct…

如何使用dlinject将一个代码库实时注入到Linux进程中

关于dlinject dlinject是一款针对Linux进程安全的注入测试工具&#xff0c;在该工具的帮助下&#xff0c;广大研究人员可以在不使用ptrace的情况下&#xff0c;轻松向正在运行的Linux进程中注入一个共享代码库&#xff08;比如说任意代码&#xff09;。之所以开发该工具&#…

扬帆优配|新概念火了!时空大数据龙头再冲击涨停

时空大数据近期受资金追捧。 今天早盘&#xff0c;中通邦本开盘后再度冲击涨停&#xff0c;一度封死涨停板&#xff0c;之后涨停打开&#xff0c;截至上午收盘仍上涨超5%。此前&#xff0c;中通邦本已接连两日涨停&#xff0c;公司在互动渠道上表示&#xff0c;参股公司北京邦本…

Spring中Bean生命周期及循环依赖

spring中所说的bean对象 与 我们自己new的对象(原始对象)是不同的&#xff1b;bean对象是指spring框架创建管理的我们的对象生命周期即&#xff1a;何时生&#xff0c;何时死1.实例化 Instantiation&#xff1a;spring通过反射机制以及工厂创建出来的原始对象&#xff1b;2.属性…

【Spring】八种常见Bean加载方式

&#x1f6a9;本文已收录至专栏&#xff1a;Spring家族学习 一.引入 (1) 概述 ​ 关于bean的加载方式&#xff0c;spring提供了各种各样的形式。因为spring管理bean整体上来说就是由spring维护对象的生命周期&#xff0c;所以bean的加载可以从大的方面划分成2种形式&#xff…

2023年融资融券研究报告

第一章 行业概况 融资融券是证券交易市场上的两种金融衍生品交易方式&#xff0c;主要用于股票、债券等证券的融资和投资。 融资是指投资者向证券公司借入资金购买证券&#xff0c;以期望股票价格上涨后卖出获得利润。融资需支付一定的利息和费用&#xff0c;利息根据借入的资…

CSS实现checkbox选中动画

前言 &#x1f44f;CSS实现checkbox选中动画&#xff0c;速速来Get吧~ &#x1f947;文末分享源代码。记得点赞关注收藏&#xff01; 1.实现效果 2.实现步骤 定义css变量&#xff0c;–checked&#xff0c;表示激活选中色值 :root {--checked: orange; }创建父容器&#xf…

python+pytest接口自动化(6)-请求参数格式的确定

我们在做接口测试之前&#xff0c;先需要根据接口文档或抓包接口数据&#xff0c;搞清楚被测接口的详细内容&#xff0c;其中就包含请求参数的编码格式&#xff0c;从而使用对应的参数格式发送请求。例如某个接口规定的请求主体的编码方式为 application/json&#xff0c;那么在…

Go 实现 AOI 区域视野管理

在游戏中,场景里存在大量的物体.如果我们把所有物体的变化都广播给玩家.那客户端很难承受这么大的压力.因此我们肯定会做优化.把不必要的信息过滤掉.如只关心玩家视野所看到的.减轻客户端的压力,给玩家更流畅的体验. 优化的思路一般是: 第一个是尽量降低向客户端同步对象的数量…

【Java】P1 基础知识与碎碎念

Java 基础知识 碎碎念安装 Intellij IDEAJDK 与 JREJava 运行过程Java 系统配置Java 运行过程Java的三大分类前言 本节内容主要围绕Java基础内容&#xff0c;从Java的安装到helloworld&#xff0c;什么是JDK与什么是JRE&#xff0c;系统环境配置&#xff0c;不深入Java代码知识…

传导EMI抑制-Π型滤波器设计

1 传导电磁干扰简介 在开关电源中&#xff0c;开关管周期性的通断会产生周期性的电流突变&#xff08;di/dt&#xff09;和电压突变(dv/dt)&#xff0c;周期性的电流变化和电压变化则会导致电磁干扰的产生。 图1所示为Buck电路的电流变化&#xff0c;在Buck电路中上管电流和下…

ubuntu 22.04 mangodb

文章写在2023年3月1日 目前最新的mangodb稳定版本是6.04 1.安装server server安装包为mangodb的程序主体。 服务器deb安装包下载地址 https://www.mongodb.com/try/download/community ubuntu22.04的server deb 文件url https://repo.mongodb.org/apt/ubuntu/dists/jammy/mo…

计算机组成原理 浮点数运算清晰明了

注释&#xff1a;阶码和尾数都需要符号位区分正负 例题1&#xff1a;x 2^-11*0.100101&#xff0c; y 2^-10*(-0.011110)&#xff0c;求xy 第零步 补码表示 对于x来说-11 补码表示为 11011&#xff1b; 0.100101补码表示为00.100101对于y来说-10补码表示为 10110&#xff…