首页 > 其他分享 >线程池详解

线程池详解

时间:2024-03-28 23:58:47浏览次数:15  
标签:状态 int 任务 详解 线程 ctl null

线程回顾

创建一个线程需要做如下两件事:

  • 继承Thread
  • 实现Runnable
public void Test(){
	Thread thread = new Thread(new Myrunnable());
    thread.start();
}

static class MyRunnable implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }

    static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }

线程的状态

  1. 新建(NEW)

    • 线程已经被创建,但还没有开始执行(也就是说,Thread.start() 方法还没有被调用)。
  2. 可运行(RUNNABLE)

    • 处于 Runnable 状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。
  3. 就绪(READY)

    • 线程已经调用了 start() 方法,但尚未被线程调度器选中去执行。它们准备好运行,并等待CPU。
  4. 运行(RUNNING)

    • 线程当前正在执行。如果线程的 run() 方法正在执行,那么它就处于这个状态。
  5. 阻塞(BLOCKED)

    • 线程在等待一个监视器锁(进入一个 synchronized 块或方法),以便进入一个同步的区域。
  6. 等待(WAITING)

    • 线程通过调用 Object.wait()Thread.join()LockSupport.park() 进入无限期等待状态,等待另一个线程的通知或中断。
  7. 定时等待(TIMED_WAITING)

    • 线程在一定的时间内等待另一个线程的动作。与WAITING不同,它会在调用如 Thread.sleep(long)Object.wait(long)Thread.join(long)LockSupport.parkNanos() / LockSupport.parkUntil() 时发生,这些调用都带有时间限制。
  8. 终止(TERMINATED)

    • 线程的运行已经结束,因为 run() 方法执行完毕或者因为异常退出。

图中还展示了线程如何从一个状态转换到另外一种状态。

eg:一个RUNNING状态的线程可能通过调用 yield() 方法回到READY状态,或者因为调用 sleep()wait() 等方法进入WAITING或TIMED_WAITING状态。等待的线程可能会在收到 notify()notifyAll()LockSupport.park();时返回到READY状态。一个处于BLOCKED状态的线程,在获取到锁之后会进入RUNNING状态。

但是一般都不会直接去继承Thread或者实现Runnable,因为这样会有一些弊端:

  1. 线程资源无法重复利用,开销较大
  2. 创建线程等待时间较长
  3. 线程无限创建可能导致内存占用过大OOM(没内存了)

线程池

根据上面的状态,普通线程执行完,就会进入TERMINATED销毁掉,而线程池就是创建一个缓冲池存放线程,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等候下次任务来临,这使得线程池比手动创建线程有着更多的优势:

  • 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;

  • 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;

  • 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM

  • 节省cpu切换线程的时间成本(需要保持当前执行线程的现场,并恢复要执行线程的现场)。

    • 保存线程现场:操作系统保存当前线程的状态信息,包括程序计数器、寄存器内容和内存状态等。
    • 恢复新线程现场:操作系统将另一个线程之前保存的状态信息恢复,以便这个线程可以继续执行。

    线程上下文切换是有成本的,因为它需要CPU周期来保存和加载线程的状态信息,并且在切换过程中CPU不做任何实际的工作,只是在两个线程之间传递。当频繁进行上下文切换时,这个开销就变得非常可观。

    线程池可以减少上下文切换的频率和成本:

    • 固定数量的线程:线程池中通常有一个固定数量的工作线程。这意味着不需要为每个新任务频繁地创建和销毁线程,从而避免了不断进行上下文切换。
    • 任务队列:线程池通常维护一个待执行任务的队列。工作线程在执行完一个任务后,可以立即从队列中取出下一个任务并开始执行,而不是结束并等待系统调度新的线程。
    • 减少延迟:由于线程已经创建好并且就绪,线程池还可以减少任务的启动延迟,因为无需等待新线程的创建。
  • 提供更强大的功能,延时定时线程池。(Timer vs ScheduledThreadPoolExecutor)

线程池体系图:

Executor

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

对于Executor只提供了最简单的执行方法。

ExecutorService

ExecutorService提供了更多的状态判断。

ThreadPoolExecutor

这个线程池是最常用的方法之一。

    @Test
    public void Test1(){
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,1, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
        MyRunnable myRunnable = new MyRunnable();
        threadPoolExecutor.execute(myRunnable);
        threadPoolExecutor.execute(myRunnable);
    }

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;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
参数名作用
corePoolSize核心线程池基本大小,核心线程数
maximumPoolSize线程池最大线程数
keepAliveTime线程空闲后的存活时间
TimeUnit unit线程空闲后的存活时间单位
BlockingQueue workQueue存放任务的阻塞队列
ThreadFactory threadFactory创建线程的工厂
RejectedExecutionHandler handler当阻塞队列和最大线程池都满了之后的饱和策略
  • corePoolSize(核心线程数)

    1. 这是线程池的基本大小,也就是在没有任务执行时线程池的大小,并且是线程池允许创建的最小线程数量。
    2. 当新任务提交给线程池时,如果当前运行的线程数少于 corePoolSize,线程池会即使没有空闲线程也会创建一个新线程来处理请求。
    3. 这些核心线程会一直存活在线程池中,即使它们处于闲置状态(除非设置了 allowCoreThreadTimeOuttrue)。
  • maximumPoolSize(最大线程数)

    1. 这是线程池允许创建的最大线程数。
    2. 当池中的线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务,直到线程数达到 maximumPoolSize
    3. 如果运行的线程数已经达到了 maximumPoolSize,新提交的任务会被拒绝处理,或者按照配置的拒绝策略来处理。
    4. 一旦线程数超过了 corePoolSize,多余的空闲线程会在一定时间内(由 keepAliveTime 参数指定)终止,以减少资源消耗。

举例来说,如果 corePoolSize 设置为10,maximumPoolSize 设置为20,那么任何时候线程池至少会有10个活跃的线程(如果有任务需要执行),即使有些线程是闲置的。当任务数增加,并且所有的核心线程都在忙时,线程池可以临时创建更多的线程,最多到20个。一旦这些额外的线程在完成任务后闲置了超过 keepAliveTime 设定的时间,它们将会被终止。

  • workQueue:阻塞队列

    1. 当线程池正在运行的线程数量已经达到corePoolSize,那么再通过execute添加新的任务则会被加workQueue队列中,在队列中排队等待执行,而不会立即执行。一般来说,这里的阻塞队列有以下几种选择: ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue;
  • keepAliveTime:线程空闲时间

    1. 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
    2. 如果allowCoreThreadTimeout=true,则会直到线程数量=0
  • threadFactory:线程工厂,主要用来创建线程

  • rejectedExecutionHandler:任务拒绝处理器,两种情况会拒绝处理任务

1:当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务

2:当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务

3:当拒绝处理任务时线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,另外在ThreadPoolExecutor类有几个内部实现类来处理这类情况

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

源码剖析

execute详解

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)
            //线程池创建失败,核心线程小于0?开什么玩笑,最大线程数还小于核心线程数不是扯蛋吗,还有哪有存活时间小于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;
    }

现在类初始化好了,得去看看执行器executor了

threadPoolExecutor.execute(myRunnable);
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
    
        int c = ctl.get();
    	//如果当前线程数量小于核心线程数量
        if (workerCountOf(c) < corePoolSize) {
            //true表示添加的为核心线程数量
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
    	//是否是运行状态,并且将需要执行的方法成功加入到等待队列当中
        if (isRunning(c) && workQueue.offer(command)) {
            //再次检查状态
            int recheck = ctl.get();  
            //发现线程池已经关闭了,移除方法
            if (!isRunning(recheck) && remove(command))
                //调用rejectedExecution
                reject(command);
            else if (workerCountOf(recheck) == 0)
                //如果没有可用线程的话(比如coreSize=0),创建一个空work
                //该work创建时不会给指派任务(为null),但是会被放入works集合,进而从队列获取任务去执行
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            //添加线程失败了
            reject(command);
    }

这里的command就是我们要处理的myRunnable方法。

int c = ctl.get();

在Java的 ThreadPoolExecutor 类中,ctl 是一个 AtomicInteger 实例,用来控制线程池的主要状态和线程数。它将这两类信息打包成一个整数以实现原子操作,这是为了线程安全性考虑,即在多线程环境中保证数据的一致性。

这里的 int c = ctl.get(); 这行代码是在获取当前的 ctl 值。该值包含两部分信息:

  1. 线程池状态(例如 RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED)
  2. 工作线程数(当前活动的线程数)

ctl 使用位运算来存储这两部分信息。高位通常存储线程池状态,低位存储工作线程数。这样设计是为了允许在单个原子操作中更新和检查线程池状态和线程数,可以保证线程安全。

举例来说,如果你想要添加一个新线程到线程池时,需要检查当前活动的线程数是否少于配置的最大线程数。如果要关闭线程池,需要检查线程池的当前状态。所有这些操作都可以通过原子地读取 ctl 的值来进行。

因此,ctl.get() 是在执行任何需要知道线程池状态和线程数的操作之前,线程安全地获取这些信息的方法。

为什么需要检测两次?

为了防止并发问题,int recheck = ctl.get(); 这里再次获取线程池的当前状态。在并发环境中,线程池的状态可能在任务提交和放入队列后发生了变化。即使刚刚检查过线程池是运行的,现在可能不是了。

通常情况下,当你调用 shutdown() 方法时,线程池会继续执行并完成所有已经在等待队列中的任务,但它不会接受任何新的任务。shutdown() 方法启动了线程池的关闭序列,但这个关闭是渐进的,不是立即终止当前执行的任务。

那为什么还需要从队列中移除任务并拒绝它们呢?这个问题的关键是在于并发和时间窗口。考虑以下的并发场景:

  1. 线程A调用 shutdown() 方法,线程池开始关闭流程。
  2. 同时,线程B提交了一个任务到线程池,而这时线程池可能还没有完全转换到关闭状态。如果任务成功地被添加到等待队列中,那么按照 shutdown() 的语义,这个任务实际上是不应该被接受的。
  3. 线程B在提交任务后,再次检查线程池状态(这是一个好的并发编程实践,确保操作的有效性)。如果此时线程B发现线程池已经关闭(可能是由线程A触发的),它就知道了自己的任务不应该被队列接受。
  4. 因此,线程B会尝试移除刚刚添加的任务,以保持状态一致性,并触发拒绝处理程序(可能是记录日志、通知用户等)。

在这种情况下,shutdown() 方法之后再次检查线程池状态和移除任务是必要的步骤,以保证线程池关闭操作的一致性和正确性,即使在多线程竞争条件下。它确保了即使在关闭命令之后的短时间内提交的任务也会被正确处理,即不会执行,因为在 shutdown() 之后提交的任务是不应该被执行的。

进程创建addWorker

//线程创建

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    //第一步,计数判断,不符合条件打回false
    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);
            //判断线程数,注意这里!
            //也就说明线程池的线程数是不可能设置任意大的。
            //最大29位(CAPACITY=29位二进制)
            //超出规定范围,返回false,表示不允许再开启新工作线程,创建worker失败!
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))//core为true,就是核心线程数,反之就是最大线程数
                return false;
            
            //增加线程数量成功
            if (compareAndIncrementWorkerCount(c))
                break retry;//去执行第二步
            
            c = ctl.get();  // 再次获取状态
            if (runStateOf(c) != rs)
                //状态发生了改变
                continue retry;//回到第一步
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    //第二步,创建新work放入线程集合works(一个HashSet)
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        //符合条件,创建新的work并包装task
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            //加锁,workers是一个hashset(非线程安全),这里要保障线程安全性
            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();
     /**
     * Set containing all worker threads in pool. Accessed only when
     * holding mainLock. 统一管理所有的worker
     */
    //private final HashSet<Worker> workers = new HashSet<Worker>();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            //更新当前线程池的最大线程数
                            largestPoolSize = s;
                        workerAdded = true;
                    }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                //注意,只要是成功add了新的work,那么将该新work立即启动,任务得到执行
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
        Runnable firstTask;

		Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        public void run() {
            runWorker(this);
        }

这里的Worker对象实现了Runnable接口的run方法,this.thread = getThreadFactory().newThread(this);创建线程的this其实也就是worker自己。

而在run方法里面调用了runWorker,其实也就是运行线程了。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();//获取当前线程
        Runnable task = w.firstTask;//获取传递过来的要执行的方法
        w.firstTask = null;
        w.unlock(); // 允许被中断
        boolean completedAbruptly = true; //默认是被打断了
        try {
            //task不为空执行task,反之去等待队列中拿一个任务出来run
            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
                //运行状态至少为STOP || (线程是否中断,清除中断状态&&线程至少为STOP)&&检查当前工作线程(wt)是否没有被中断。
                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;
           p         } 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);
        }
    }
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?
            //判断当前线程是否需要被淘汰
            //1.是否允许核心线程被淘汰	 2.工作线程数是否大于核心线程数
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			 //线程数超出max,并且上次循环中poll等待超时了,那么说明该线程已终止
        	 //将线程队列数量原子性减
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                //计数器做原子递减,减少线程数,返回null,for被中止,
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    //在keepAliveTime-TimeUnit.NANOSECONDS时间内没有任务拿出来就return null
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                	//没有任务就会一直阻塞,直到有任务为止
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

Executors

以上构造函数比较多,为了方便使用,juc提供了一个Executors工具类,内部提供静态方法

1)newCachedThreadPool( ) : 弹性线程数

2)newFixedThreadPool(int nThreads) : 固定线程数

3)newSingleThreadExecutor( ) : 单一线程数

4)newScheduledThreadPool(int corePoolSize) : 可调度,常用于定时

标签:状态,int,任务,详解,线程,ctl,null
From: https://blog.csdn.net/2301_79516932/article/details/137128120

相关文章

  • S7Comm报文详解
    S7协议是西门子公司为其S7系列PLC(可编程逻辑控制器)通信而设计的一种专用协议。S7协议主要用于西门子PLC之间的通信,以及PLC与其他设备的通信。该协议支持多种通信方式,如MPI(多点接口)、PROFIBUS和IndustrialEthernet等。S7协议的报文结构相对复杂,可分为多个层次。1.简介对比OSI参......
  • 【Linux】线程同步{死锁/线程同步相关接口/由浅入深理解线程同步}
    文章目录1.死锁1.1概念1.2死锁的必要条件2.线程同步相关接口2.1pthread_cond_init/destroy()2.2intpthread_cond_wait2.3linux下的条件变量及其作用2.4intpthread_cond_signal/broadcast();2.5Linux下阻塞和挂起的异同2.6阻塞,挂起,和进程切换的关系3.由浅入深理解线......
  • 线程创建方式、构造方法和线程属性
    欢迎各位!!!推荐PC端观看文章重点:学会五种线程的创造方式目录1.开启线程的五种方式2.线程的构造方法3.线程的属性及获取方法1.开启线程的五种方式创造线程的基本两步:(1)使用run方法记录线程要做的任务(2)使用线程的引用调用start开启线程1.1.继承Tread,重写runclassm......
  • 【docker常用命令系列】Docker save语法用法示例详解
    【docker常用命令系列】Dockersave语法用法示例详解源自专栏《docker常用命令系列目录导航?》文章目录[【docker常用命令系列】Dockersave语法用法示例详解](https://zhuanlan.zhihu.com/p/689619518/)概览用法别名选项示例参考链接概览dockerimagesav......
  • Java抽象类详解:定义、特性与实例化限制(day12)
    抽象类总结一下今天老师上课的内容,前面几节课听得是有点懵,在讲到内存问题,也就是代码在栈、堆、以及方法区是怎么执行的,听得不是很懂,今天讲到抽象类以及重写的机制,似乎开始慢慢懂得了java的底层原理。父类:子类:上面的Cat类重写父类的eat()方法。Test:如果我们将父类的......
  • 【AOP技术之穿透版】AOP详解
    这篇文章将让你知道AOP相关详细知识,分为基础和进阶,看完会觉得AOP也不过如此。。。制作不易,觉得不错请点赞收藏!!!目录1.AOP基础1.1 AOP概述1.2 事务管理1.3 什么又是面向方法编程呢,为什么又需要面向方法编程呢?1.4 AOP的作用(无侵入性:解耦)1.5  AOP快速入......
  • Mysql(数据库)知识详解【4】~{索引,主键优化}
    记住满元素中间元素向上裂变就行了因为如果是5个节点,比第一个节点小的算一个指针,逼最后一个节点大的算一个指针,里面是4个指针所有元素都会出现在叶子节点并且诸多叶子节点通过指针构造一张单项链表看我:除了最下面节点,上面的节点(叶子空间最大16k)全部放满内存......
  • 探索多种数据格式:JSON、YAML、XML、CSV等数据格式详解与比较
    1.数据格式介绍数据格式是用于组织和存储数据的规范化结构,不同的数据格式适用于不同的场景。常见的数据格式包括JSON、YAML、XML、CSV等。数据可视化|一个覆盖广泛主题工具的高效在线平台(amd794.com)https://amd794.com/jsonformat2.JSON(JavaScriptObjectNotation)......
  • top命令找到占用CPU最高的java线程
    1、使用jps查找正在运行的java进程2、通过使用top命令查找该线程下CPU使用最高的线程top-Hppid:即  top-Hp2860 3、TIME列就是各个Java线程耗费的CPU时间,显然CPU时间最长的是ID为2968的线程,用printf"%x\n"2968可得到2968的十六进制值为:b984、终于轮到jsta......
  • 【机器学习】决策树学习下篇(详解)
    引言在当今数据驱动的时代,机器学习技术已成为解决复杂问题不可或缺的工具。其中,决策树学习作为一种基础且强大的算法,广泛应用于各种领域,包括但不限于金融风控、医疗诊断、客户关系管理等。决策树以其简单直观、易于理解和实现的特点,受到了数据科学家和业界专家的青睐。过拟合......