首页 > 编程语言 >史上最强Java面试八股文合集,持续更新!

史上最强Java面试八股文合集,持续更新!

时间:2024-10-22 13:49:33浏览次数:7  
标签:Runnable Java Worker 任务 线程 shutdown 八股文 null 合集

线程池

核心参数

补充: 还有一个参数 threadFactory(线程工厂):

用于创建新线程的工厂,通常用于给线程设定名称、设置为守护线程等。默认的线程工厂会创建一个普通的、非守护线程。

ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(
    					3, // 核心线程数, 默认情况下, 当前线程数量不超过核心线程数时, 空闲超时后也不会被回收
                        5, // 最大线程数
                        10, // 空闲worker线程多久后销毁
                        TimeUnit.SECONDS, // 时间单位
                        new ArrayBlockingQueue<>(10), // 任务队列
                        new ThreadPoolExecutor.DiscardOldestPolicy()); // 拒绝策略

for (int i = 0; i < 20; i++) {
    threadPoolExecutor.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    });
}

threadPoolExecutor.shutdown();

try {
    if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
        // 如果超时,尝试立即关闭线程池
        threadPoolExecutor.shutdownNow();
        // 再次等待所有任务执行完成
        if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
            System.err.println("线程池未能完全关闭");
        }
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

  1. Executors.FixedThreadPool: 返回一个ExcutorService, ThreadPoolExcutor 实际上也是一个 ExcutorService

    • 核心线程数和最大线程数相等且可配置; 无过期时间

    • 适用场景:当需要控制并发线程数量

    • 和CachedThreadPool相比, 线程数量上限可控, 防止同时存在的线程数量过多导致资源耗尽;

      但任务过多时, 无法动态地增加线程数量;

    • 典型应用:适用于需要处理大量时间较长的任务;

  2. CachedThreadPool:

    • 核心线程数是0, 最大线程数Integer.MAX_VALUE, 线程空闲60s被销毁

    • 使用的任务队列是SynchronousQueue 没有存储元素的能力,不保存任何元素。

      当一个线程试图将元素放入 SynchronousQueue 时,它会阻塞直到另一个线程试图从队列中取出这个元素。

    • 适用场景:适用于需要处理大量短生命周期的, 具有突发性的任务;

    • 典型应用:?

  3. SingleThreadExecutor:

    • 只有一个线程, 最大线程数量也是1;
    • 适用场景:适用于需要保证任务按照顺序执行的场景, SingleThreadExecutor 只会使用单个工作线程来执行任务,保证了任务之间的顺序性。
    • 典型应用:适用于需要顺序执行的任务,例如按照时间顺序保存日志, 要求保存顺序和日志发生顺序一致;

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

  1. ScheduledThreadPoolExecutor

    • 可以定时执行任务, 原理是用堆实现无界的优先队列, 按执行时间对任务排序; Worker线程循环取任务, 如果队头任务还没到时间, Worker阻塞;
    • 有一个专门的线程负责检查队头任务, 如果到期, 唤醒Worker;
    • 新添加任务时, 唤醒Worker;
    • 核心线程数可配置, 最大线程数 Integer.MAX_VALUE, 超时时间0, 也就是说取不到任务立即被回收;
拒绝策略
  1. 需要实现RejectedExecutionHandler, 重写rejectedExecution方法

  2. ThreadPoolExecutor提供了几种现成的

  3. 默认是AbortPolicy

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        // 由调用ThreadPoolExecutor.execute()方法的线程自己去执行r.run();
    	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    	    if (!e.isShutdown()) {
    	        r.run();
    	    }
    	}
    }
    public static class AbortPolicy implements RejectedExecutionHandler {
    	// 丢弃任务并抛出异常
    	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    	    throw new RejectedExecutionException("Task " + r.toString() +
    	                                         " rejected from " +
    	                                         e.toString());
        }
    }
    public static class DiscardPolicy implements RejectedExecutionHandler {
    	// 丢弃任务, 不抛异常
    	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    	}
    }
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        // 将任务队里中最早入队的任务丢弃, 添加新的任务
    	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    	    if (!e.isShutdown()) {
    	        e.getQueue().poll();
    	        e.execute(r);
    	    }
    	}
    }
    

添加任务的流程
  1. 并非一开始就创建了所有核心线程, 而是添加任务时一个一个创建的;
  2. 任务队列排满以后创建非核心线程执行任务, 这会导致后来的任务在任务队列里的任务之前执行;

在这里插入图片描述

属性与状态
// AtomicInteger, 使用CAS实现的线程安全的Integer
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; // 29
// 低29位表示工作线程的个数
// 1 << 29 再 - 1, =29位全1, 表示工作线程个数的最大值
private static final int CAPACITY   = (1 << COUNT_BITS) - 1; 

// 前三位表示状态, 有符号数
// 正常运行, 可以添加任务
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;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

源码解读
execute( )
  • 注意一个地方, 在并发情况下, 可能出现一个线程中向线程池内添加任务, 而另一个线程执行了shutdown的情况; 这种情况可能导致在shutdown状态下, 线程池中工作线程已经被销毁的情况下, 又往工作队列中添加了新的任务;
  • 所以往任务队列中添加任务成功以后, 需要再次检查状态和工作线程个数;
  • 如果状态是shutdown, 会尝试移除新添加的任务, 如果移除失败, 并且已经没工作线程, 则添加一个工作线程;
if (command == null)
	throw new NullPointerException();
	// 获取状态
	int c = ctl.get();
	// 如果当前核心线程数量 < 最大核心线程数量, 尝试创建核心线程并执行新添加的任务
	if (workerCountOf(c) < corePoolSize) {
	    if (addWorker(command, true))
	        return;
        //(如果状态不是RUNNING或者因为并发导致创建失败), 再次获取状态并向下执行
	    c = ctl.get();
	}
	// 如果是Running状态, 向任务队列添加任务
	if (isRunning(c) && workQueue.offer(command)) {
        // 添加任务后重新再次检查状态, 防止并发状态下线程池状态被修改;
	    int recheck = ctl.get();
        // 如果状态已经不是running, 尝试移除任务
	    if (!isRunning(recheck) && remove(command))
            // 如果移除成功, 调用拒绝策略
	        reject(command);
        // 如果当前是Running状态 或者 移除失败, 并且已经没有worker, 添加一个非核心线程, 去执行任务队列中的任务;
        // 防止在shutdown状态下无法处理任务队列中的任务, 进而无法进入tidying状态;
	    else if (workerCountOf(recheck) == 0)
	        addWorker(null, false);
	}
	// 如果不是Running 或者 任务添加失败, 尝试创建非核心线程并执行新添加的任务;
	else if (!addWorker(command, false))
        // 如果创建非核心线程失败, 执行拒绝策略;
	    reject(command);
}

addWorker( )
retry:
for (;;) {
    int c = ctl.get();
    int rs = runStateOf(c);
	// rs状态不是Running, 并且不是(shutdown且无worker且有任务), 那么不创建线程, 直接退出
    if (rs >= SHUTDOWN &&
        ! (rs == SHUTDOWN &&
           firstTask == null &&
           ! workQueue.isEmpty()))
        return false;
	// 内层for循环判断线程数量
    for (;;) {
        // 先判断总线程数量
        // 然后如果当前要创建核心线程, 判断是否超出核心线程数量
        // 如果是非核心线程, 判断是否超出最大线程数量
        int wc = workerCountOf(c);
        if (wc >= CAPACITY ||
            wc >= (core ? corePoolSize : maximumPoolSize))
            return false;
        // CAS方式将工作线程数 + 1, 跳出外层循环, 进入创建工作
        if (compareAndIncrementWorkerCount(c))
            break retry;
        // 如果并发导致失败, 再次判断状态, 如果和之前的状态一致, 继续内层循环判断数量; 如果状态变化, 重新进行外层循环
        c = ctl.get();  // Re-read ctl
        if (runStateOf(c) != rs)
            continue retry;
    }
}

// 添加worker并启动该线程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
    // new worker时创建一个线程, 绑定到thread属性
    w = new Worker(firstTask);
    final Thread t = w.thread;
	// 几乎一定会进入, 健壮性判断
    if (t != null) {
        final ReentrantLock mainLock = this.mainLock;
        // 防止在添加线程时执行了shutdown或者shutdownNow, 这两个函数里也是有锁mainLock的;
        mainLock.lock();
        try {
			// 拿到最新的线程池状态
            int rs = runStateOf(ctl.get());
			// 如果线程池状态为Running或者是前面说过的shutdown + 有任务 + 无worker
            if (rs < SHUTDOWN ||
                (rs == SHUTDOWN && firstTask == null)) {		
                // 健壮性判断, 无所谓
                if (t.isAlive())
                    throw new IllegalThreadStateException();
                // 创建线程, workers是一个set, 保存所有的工作线程
                workers.add(w);
                int s = workers.size();
                // 记录了运行过程中工作线程的最大个数
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        // 添加工作线程成功, 启动工作线程, 设置标记
        if (workerAdded) {
            t.start();
            workerStarted = true;
        }
    }
} finally {
    // 如果启动工作线程失败, 如果已经添加到workers, 删除, 将工作线程数 - 1;
    // 然后尝试修改线程池状态到TYDING状态
    // 不重要
    if (! workerStarted)
        addWorkerFailed(w);
}
// 返回当前线程是否启动成功
return workerStarted;

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

Worker类
  • 继承了AQS, 实现了Runnable, Runnable很好理解, 因为工作线程要执行任务, 这个任务就是run方法;

  • 持有一个 Thread对象, Worker 就是放到这个它持有的 Thread对象里运行的;

  • 继承AQS是为了在shutdown时根据Worker的不同状态进行不同的处理;

    shutdown方法会对每个worker线程尝试执行tryLock方法获取锁; 成功获取到锁则设置中断标记, 获取失败表明当前worker正在工作, 不允许中断

    AQS 初始时 state = -1, shutdown无法中断此worker

    runWorker方法一开始通过 unlock 设置 state = 0, shutDown可以中断

    一旦在runWoker方法中取到任务, 又会通过lock方法改state = 1; 表示不可以中断, state = 1时, shutdown再尝试lock会失败;

    所以继承AQS是为了实现一种针对shutdown的锁的机制

    那shutdownNow怎么办? shutdownNow有两个位置, 一个是将状态改为STOP, 而runWorker方法内每次执行任务之前都会判断当前状态是否>= STOP, 如果是, 会自己设置interrupt位; 另一个是在shutdownNow方法内, 会直接遍历所有worker, 直接调用interrupt方法

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
{
    private static final long serialVersionUID = 6138294804551838833L;
	// 持有一个线程对象
    final Thread thread;
    // 当前正在执行的任务
    Runnable firstTask;

    Worker(Runnable firstTask) {
        // AQS属性设置为-1; 标志当前线程刚刚初始化, 还未执行runWorker方法
        // 这样的线程不允许被中断
        setState(-1);
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
	
    public void run() {
        runWorker(this);
    }

runWorker
// runWorker方法是Worker类的方法, 方法传参是this, 即 runWorker(this)
final void runWorker(Worker w) {
    // 拿到的是Wordker持有的Thread, 这两个一一对应, 所以可以直接简称worker线程
	Thread wt = Thread.currentThread();
    // 拿到任务, 两种情况, 一个是task = null这时应该去任务队列拿任务, 一种是线程<核心线程数,直接拿到了任务;
	Runnable task = w.firstTask;
	w.firstTask = null;
	w.unlock(); // 这个unlock方法是重写过的, 最终是设置AQS的state置为0, 表示此工作线程允许被中断
	boolean completedAbruptly = true;
	try {
        // 如果task != null, 直接执行, 如果是null, 从任务队列中取任务(仍有可能是null)
        // getTask返回null时, 当前线程被回收
	    while (task != null || (task = getTask()) != null) {
            // 被lock, state = 1; 此时不允许被中断
	        w.lock();
            // 如果当前状态 >= STOP(STOP, TYDING, TERMINATE), 后面的判断是为了应对并发状况
	        if ((runStateAtLeast(ctl.get(), STOP)||(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) 
                // 并且没有设置中断标志
                &&!wt.isInterrupted()){
                // 设置中断标记
	            wt.interrupt();
            }
			
            // STOP状态判断完毕, 执行任务;
	        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++;
                // unlock, 标记当前线程可以被中断
	            w.unlock();
	        }
	    }
	    completedAbruptly = false;
	} finally {
	    processWorkerExit(w, completedAbruptly);
	}
}

getTask( )
boolean timedOut = false; // Did the last poll() time out?
// 死循环尝试取任务, 返回null则线程被回收
for (;;) {
    int c = ctl.get();
    int rs = runStateOf(c);

    // 如果状态 >= STOP 或者 状态为shutdown且任务队列为空, 都可以直接中断当前线程
    if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
        decrementWorkerCount();
        return null;
    }

    int wc = workerCountOf(c);
    
	// 如果当前状态是Running, 正常运行, 那么判断数量
    // 标记当前线程是否允许被中断, 一般不允许 线程数 <= 核心线程数时被中断
    boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
	// 如果当前线程数 > 核心线程数, 当前线程空闲时间超时, 并且当前线程数 > 1 或者 任务队列为空, 中断当前线程
    if ((wc > maximumPoolSize || (timed && timedOut))
        && (wc > 1 || workQueue.isEmpty())) {
        // CAS操作将当前线程数--;
        if (compareAndDecrementWorkerCount(c))
            return null;
        continue;
    }
	
    try {
        // 当前线程允许被中断吗? 允许则通过poll方法设置最大等待时间去取任务;
        Runnable r = timed ?
            workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
        	// 不允许则一直阻塞等待拿任务;(当前线程数 <= 核心线程数)
            workQueue.take();
        if (r != null)
            return r;
         // 如果poll方法超过等待时间没取到任务, 设置超时标记
        timedOut = true;
    } catch (InterruptedException retry) {
        timedOut = false;
    }
}

shutdown or Now
  • shutdown和shutdownNow都不再接收新任务;
  • shutdown当前正在执行的任务都会执行完毕; shutdownNow 会尝试停止当前正在执行的任务;
  • shutdown方法会确保任务队列中所有任务被处理; shutdownNow不会;
  • 在runWorker方法中, 如果getTask方法返回null, 则当前线程被回收; 如果当前状态是shutdown, 并且任务队列为空时, return null; 如果当前状态是STOP, 直接return null;

线程池就是随着应用启动一直运行, 很少有需要 shutdown 的场景; 也不要动态去创建;

考点

核心线程数的数量设置为多少

CPU 密集型 = 核心数 + 1

IO 密集型 = n * 核心数; n = RT / CPU 繁忙时间, 配到 50, 100都行;

底层公式: 线程数 = 核心数 * 期望利用率 * ( 1 + IO时间 / CPU时间 );

如果 IO 密集和 CPU 密集的任务共存呢?

设置两个线程池, 一个配 核心数 + 1, 一个配 n * 核心数;

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

舱壁模式: 不同的业务使用自己的线程池, 这样一个业务故障不会影响其它线程池; 例如 Sentinel 中就有用到

需要动态调整线程池的大小吗? 不要, 就一个静态的, static new 出来就不要变了

获取核心数
Runtime.getRuntime().availableProcessors();
  • 1

JDK8 2014年发布的, Docker 那时候还没出;

如果在Docker里面跑 JDK8, 获取核心线程数还是获取的机器本身的核心数, 而非Docker的核心数;

JDK8 的高版本解决了这个问题, 但是收费;

解决: 写死核心线程数, 公司的Docker配置基本不会动的;

任务队列大小设置?

任务队列本质是放在内存的, 不要太大, 过长性能会有影响; 比如 ArrayBlockingQueue, 队头取元素需要 On 来移动;

设置成 LinkedBlockingQueue 合适一些, 因为从队头取元素, LinkedBlockingQueue 更快;

大小建议设置为512 或者 1024;

拓展: 如果有些地方用到 HashMap 和 Set , 他们的大小没有限制, 因为大了并不会导致效率降低;

最大线程数设成多少?

直接和核心线程数设置成一样;

推荐写法

在这里插入图片描述

submit 和 excute 的区别

submi 可以提交有返回值的任务, 比如可以直接提交一个 Callable 实现类对象, 也可以提交一个 Runnable;

submit 方法返回一个 Future 对象, 通过这个对象来获取返回值;

原理是构造一个 FutureTask, 里面放我们传入的 Callable 或 Runable 实现类对象;

如果提交的是 Runnable, 需要进行转换, 转换成一个 Callable, 才能保存到 FutureTask, 需要设置一个结果值, 默认是null, 我们也可以在 submit 的时候传入; Runnable 执行完成后我们通过get, 拿到的就是这个设置好的默认结果值;

转换Callable 的时候, 实际上就是做了一个 Callable实现类, 其 call 方法先执行我们传入的 Runnable 的 run 方法, 然后返回我们预先设置的结果值

ThreadPoolExecutor::excute 提交没有返回值的任务 Runnable;

当一个任务阻塞时, Worker会转而执行别的任务吗? – 不会

这种调用方式, 比先调一个拿到结果, 再调另一个, 更快;

虽然执行了8个阻塞2S的任务, 但整个运行时间也就 2S 多点; 如果一个一个调用那要 16S;

List<FutureTask> list;
for(int i = 0; i < 8; i++){
    submit(list.nextFutureTask());
}

for(int i = 0; i < 8; i++){
    list.nextFutureTask().get();
}

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

优点
  1. 降低资源消耗:线程池能够重复利用已创建的线程,避免了频繁创建和销毁线程的开销。这样可以减少系统资源的消耗,提高资源利用率。
  2. 提高响应速度:线程池中的线程可以提前创建好并且保持在就绪状态,当任务到达时可以立即执行,减少创建线程的等待时间;
  3. 提高系统稳定性:线程池可以限制线程的数量,可以防止因为线程过多导致系统资源不足而造成系统崩溃的情况
添加任务的流程

在这里插入图片描述

运行状态回收 Worker 的依据
  1. 如果 getTask 返回 null, 则回收 Worker ;

  2. 在正常运行时, 有一个循环, 循环内,

  3. 首先, 会有 allowCoreThreadTimeOut 参数和 当前工作线程数量是否大于核心线程数量 来判断当前线程是否允许被回收;

  4. 如果允许回收, 并且上一次用 poll 取任务是超时的, 并且 ( 当前还有其它工作线程 || 任务队列为空) , 则返回null;

    当前还有其它工作线程 || 任务队列为空, 这是为了防止有其它线程在同一时间 修改状态为 shutdown

  5. 如果上面的判断没有进入, 就会去取任务, 根据是否允许回收采用 带超时时间(就是 keepAliveTime)的 poll (允许回收) 或者 take(不允许回收);

shutdown 和 shutdownNow
  1. shutdown 回收的本质, 还是通过 getTask 实现的; getTask 返回 null, 则 worker 的 run 方法退出循环, 自然结束运行;

  2. getTask 方法发现当前是 STOP(也就是shutdownNow对应的状态), 直接return null;

  3. 如果当前是 shutdown, 会判断一下是不是还有任务, 没有任务就 return null;

  4. Worker 线程继承了 AQS, 实现了不可重入的锁机制; 当 Woker 线程取到任务时, 会调用自己的 lock 方法加锁, 然后去执行任务;

  5. shutdown 方法尝试中断 Worker 线程时, 先调用 tryLock 方法, 获取锁成功以后才能设置 interrupt标志 ; 所以, 正在执行任务的 Worker 不会被shutdown打断;

  6. 而 shutdownNow 方法, 直接遍历所有worker, 调用 interrupt 方法终止任务执行; 至于正在执行任务的 Worker 到底能不能终止, 就要看我们提交的任务里, 是否设计了基于 interrupt 标志来结束任务执行的代码;

  7. 总的来说, shutdown 不接受新任务, 正在进行的任务会处理完成, 任务队列中的任务会处理完成; 是一种优雅的关闭;

  8. 而 shutdownNow, 不接受新任务, 正在进行的任务会尝试终止(正在工作的 worker 设置了 interrupt ), 任务队列中的任务不会处理;

运行原理
  1. 底层采用阻塞队列保存任务, 添加任务的时候, …
  2. 工作线程 Worker 继承了 AQS, 实现了 Runnable, 持有一个 Thread;
  3. 通过将自己放到持有的 Thread 对象中运行;
  4. Worker 的 run 方法是在循环中不断的调用 getTask 取 Runnable 任务, 然后调用其 run 方法; (如果是submit 提交的 Callable 任务, 被封装成 FutureTask, 还是一个 Runnable)
  5. 取到任务以后会上锁, 锁自己; 这是为了不被 shutdown 打断;
  6. 如果 getTask 返回的值为 null, 工作线程的 run 方法的循环就会退出, Worker 线程执行完毕, 也就会被销毁;

     篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

    需要全套面试笔记【点击此处即可】免费获取

标签:Runnable,Java,Worker,任务,线程,shutdown,八股文,null,合集
From: https://blog.csdn.net/2401_87705025/article/details/143154414

相关文章

  • Java相关面试题(2024大厂高频面试题系列)
    一、多线程基础基础知识1.并发编程1.1并发编程的优缺点优点:充分利用多核CPU的计算能力,通过并发编程的形式将多核CPU的计算能力发挥到极致,性能得到提升。方面进行业务的拆分。提高系统并发能力和性能:高并发系统的开发,并发编程会显得尤为重要,利用好多线程机制可以大大提高......
  • Spring AI : Java写人工智能的应用框架
    SpringAI:为Java开发者提供高效集成大模型能力的框架当前Java调用大模型时,面临缺乏优质AI应用框架的挑战。Spring作为资深的Java应用框架提供者,通过推出SpringAI来解决这一问题。它借鉴了langchain的核心理念,并结合了Java面向对象编程的特点,为开发者提供了统一且可灵活替换......
  • vue-springboot基于JavaWeb的智慧养老院管理系统的设计与实现 附源码
    目录项目介绍系统实现截图源码获取地址下载技术栈开发核心技术介绍:为什么选择最新的Vue与SpringBoot技术核心代码部分展示项目介绍该系统从三个对象:由管理员和家属、护工来对系统进行设计构建。主要功能包括:个人信息修改,对家属信息、护工信息、老人入住、外出报备、......
  • JavaScript从零学起 —— 数据类型(进阶篇6)
    说明:此文章用作个人学习记录,若有任何问题或建议欢迎大家在评论区讨论文章目录前言一、日期(Date)1.Date类型的定义2.创建Date3.常用方法4.日期格式化5.常见问题与解决方案二、正则表达式(RegExp)1.正则表达式的定义2.创建正则表达式3.匹配常用字符4.常......
  • Java中的控制结构中的顺序结构和分支结构
    文章目录前言1.顺序结构2.分支结构2.1选择语句if2.2if格式12.3if语句格式22.3if语句格式3if语句与三元运算符2.1选择语句switch前言我是一名来自延边大学人工智能专业的热爱街舞的大四学生,目前在学习Java,我会根据我的学习路线给大家分享学习经验,这章我们学习Java中......
  • Java项目中引入达梦数据库依赖驱动并且操作链接数据库
    本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)Java项目中引......
  • 【开源免费】基于SpringBoot+Vue.JS读书笔记共享平台(JAVA毕业设计)
    本文项目编号T029,文末自助获取源码\color{red}{T029,文末自助获取源码}......
  • 【开源免费】基于SpringBoot+Vue.JS母婴商城系统 (JAVA毕业设计)
    本文项目编号T030,文末自助获取源码\color{red}{T030,文末自助获取源码}......
  • 【JAVA】第二张_面向对象
    引言Java面向对象是一种编程方法论,它将软件系统看作是一组对象的集合,每个对象都有自己的状态和行为,并且能够相互之间进行通信和协作。java面向对象知识的思维导图1、面向对象思想面向对象(Object-Oriented)是一种编程思想和方法论,它的核心理念是将问题拆解成由对象组成的模......
  • 基于Java+Jsp+Ssm+Mysql实现的零食商城系统功能设计与实现一
    一、前言介绍:1.1项目摘要随着电子商务的快速发展和消费者购物习惯的改变,线上购物已成为人们日常生活中不可或缺的一部分。零食作为日常生活中常见的消费品,其线上销售市场也呈现出快速增长的趋势。零食商城系统的设计与实现,旨在满足消费者对零食的多样化、个性化需求,提供......