点击关注公众号,实用技术文章及时了解

在日常的开发工作中,我们经常会需要使用到线程池这类型的组件。例如下边几种应用场景:

线程池经典应用场景

异步发送邮件通知发送一个任务,然后注入到线程池中异步发送。

心跳请求任务创建一个任务,然后定时发送请求到线程池中。

类似的场景有很多,我们下边一步一步地来介绍不同的应用场景下,线程池的具体使用案例:

异步发送邮件场景

定义一个简单的邮件发送接口:

public interface SendEmailService {/*** 发送邮件** @param emailDTO 邮件对象*/void sendEmail(EmailDTO emailDTO);
}

接着是邮件发送的简单实现类:

@Service
public class SendEmailServiceImpl implements SendEmailService {@Resourceprivate ExecutorService emailTaskPool;@Overridepublic void sendEmail(EmailDTO emailDTO) {emailTaskPool.submit(() -> {try {System.out.printf("sending email .... emailDto is %s \n", emailDTO);Thread.sleep(1000);System.out.println("sended success");} catch (InterruptedException e) {e.printStackTrace();}});}
}

邮件的发送逻辑通过一个简单的线程睡眠来模拟发送过程中的耗时操作。

然后是线程池方面的配置:

@Configuration
public class ThreadPoolConfig {@Beanpublic ExecutorService emailTaskPool() {return new ThreadPoolExecutor(2, 4,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(), new SysThreadFactory("email-task"));}
}

controller模块的触发

@RestController
@RequestMapping(value = "/test")
public class TestController {@Resourceprivate SendEmailService sendEmailService;@GetMapping(value = "/send-email")public boolean sendEmail() {EmailDTO emailDTO = new EmailDTO();emailDTO.setContent("测试文案");emailDTO.setReceiver("idea");emailDTO.setTitle("邮件标题");sendEmailService.sendEmail(emailDTO);return true;}
}

这是一个非常简单的案例,通过一个http请求,然后触发一个邮件的发送操作。

心跳请求场景

这类应用场景一般会在一些基础组件中使用到,例如一些具有心跳探活机制类型功能的中间件,如nacos。下边来看看对应的代码实践:首先是心跳模块代码:

public class HeartBeatInfo {private String info;private long nextSendTimeDelay;public String getInfo() {return info;}public void setInfo(String info) {this.info = info;}public long getNextSendTimeDelay() {return nextSendTimeDelay;}public void setNextSendTimeDelay(long nextSendTimeDelay) {this.nextSendTimeDelay = nextSendTimeDelay;}@Overridepublic String toString() {return "HeartBeatInfo{" +"info='" + info + '\'' +", nextSendTimeDelay=" + nextSendTimeDelay +'}';}
}

然后是模拟一个心跳包的发送服务接口定义:

public interface HeartBeatTaskService {void sendBeatInfo();
}

接下来是心跳任务的发送核心部分实现:

@Service
public class HeartBeatTaskServiceImpl implements HeartBeatTaskService {@Resourceprivate ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;@Overridepublic void sendBeatInfo() {HeartBeatInfo heartBeatInfo = new HeartBeatInfo();heartBeatInfo.setInfo("test-info");heartBeatInfo.setNextSendTimeDelay(1000);scheduledThreadPoolExecutor.schedule(new HeartBeatTask(heartBeatInfo),heartBeatInfo.getNextSendTimeDelay(), TimeUnit.MILLISECONDS);}class HeartBeatTask implements Runnable {private HeartBeatInfo heartBeatInfo;public HeartBeatTask(HeartBeatInfo heartBeatInfo) {this.heartBeatInfo = heartBeatInfo;}@Overridepublic void run() {System.out.println("发送心跳数据包:" + heartBeatInfo.getInfo());HeartBeatInfo heartBeatInfo = new HeartBeatInfo();heartBeatInfo.setInfo("test-info");heartBeatInfo.setNextSendTimeDelay(1000);scheduledThreadPoolExecutor.schedule(new HeartBeatTask(heartBeatInfo),heartBeatInfo.getNextSendTimeDelay(), TimeUnit.MILLISECONDS);}}
}

在核心实现的内部有一个延时线程池ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor会在放入线程任务的一段指定的时间之后才触发任务的执行:

@Configuration
public class ThreadPoolConfig {@Beanpublic ScheduledThreadPoolExecutor  scheduledThreadPoolExecutor(){return new ScheduledThreadPoolExecutor(2, new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);thread.setDaemon(true);thread.setName("org.idea.threadpool.beat.sender");return thread;}});}
}

JDK内部线程池的设计

看了上边两个简单的案例之后,不知道你是否会有好奇:

到底线程池的内部运行机制会是怎样的呢?

简单手写一个单消费者任务处理模型

这里我们可以通过一段简单的代码来学习这部分的内容:首先,我们将需要处理的任务封装在一个对象内部,暂时定义如下所示:

public class AsyncHandlerData {private String dataInfo;public String getDataInfo() {return dataInfo;}public void setDataInfo(String dataInfo) {this.dataInfo = dataInfo;}@Overridepublic String toString() {return "AsyncHandlerData{" +"dataInfo='" + dataInfo + '\'' +'}';}
}

然后会有一个专门消费这些个任务的service:

public interface AsyncHandlerService {/*** 任务放入队列中* * @param asyncHandlerData*/void putTask(AsyncHandlerData asyncHandlerData);
}

最后根据提前定义好的接口编写一个实现类,此时将相关的任务处理逻辑规整到了一个对象当中:

@Service
public class AsyncHandlerServiceImpl implements AsyncHandlerService, CommandLineRunner {private volatile TaskQueueHandler taskQueueHandler = new TaskQueueHandler();@Overridepublic void putTask(AsyncHandlerData asyncHandlerData) {taskQueueHandler.addTask(asyncHandlerData);}@Overridepublic void run(String... args) throws Exception {Thread thread = new Thread(taskQueueHandler);thread.setDaemon(true);thread.start();}public class TaskQueueHandler implements Runnable {private BlockingQueue<AsyncHandlerData> tasks = new ArrayBlockingQueue<>(1024 * 1024);public void addTask(AsyncHandlerData asyncHandlerData) {tasks.offer(asyncHandlerData);}@Overridepublic void run() {for (; ; ) {try {AsyncHandlerData asyncHandlerData = tasks.take();System.out.println("异步处理任务数据:" + asyncHandlerData.getDataInfo());} catch (InterruptedException e) {e.printStackTrace();}}}}
}

整个代码的思路逻辑比较简单,大致可以归整成下图所示:

整体的设计模式就是一端放入,由单个消费者取出。但是存在一个不足点,一旦消费者能力较弱,或者出现任务堵塞的话,就会导致任务队列出现堆积,然后越堆积越难处理地过来。

但是这样的设计还是一个过于简单的模型,下边我们来看看jdk内部线程池的设计模式:

线程池内部的源代码分析

我们在项目里使用线程池的时候,通常都会先创建一个具体实现Bean来定义线程池,例如:

@Bean
public ExecutorService emailTaskPool() {return new ThreadPoolExecutor(2, 4,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(), new SysThreadFactory("email-task"));
}

ThreadPoolExecutor的父类是AbstractExecutorService,然后AbstractExecutorService的顶层接口是:ExecutorService。

就例如发送邮件接口而言,当线程池触发了submit函数的时候,实际上会调用到父类AbstractExecutorService对象的java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable)方法,然后进入到ThreadPoolExecutor#execute部分。

@Override
public void sendEmail(EmailDTO emailDTO) {emailTaskPool.submit(() -> {try {System.out.printf("sending email .... emailDto is %s \n", emailDTO);Thread.sleep(1000);System.out.println("sended success");} catch (InterruptedException e) {e.printStackTrace();}});
}

java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable) 源代码位置:

/*** @throws RejectedExecutionException {@inheritDoc}* @throws NullPointerException       {@inheritDoc}*/
public Future<?> submit(Runnable task) {if (task == null) throw new NullPointerException();RunnableFuture<Void> ftask = newTaskFor(task, null);execute(ftask);return ftask;
}

这里面你会看到返回的是一个future对象供调用方判断线程池内部的函数到底是否有完全执行成功。因此如果有时候如果需要判断线程池执行任务的结果话,可以这样操作:

Future future = emailTaskPool.submit(() -> {try {System.out.printf("sending email .... emailDto is %s \n", emailDTO);Thread.sleep(1000);System.out.println("sended success");} catch (InterruptedException e) {e.printStackTrace();}});//todo somethingfuture.get();
}

在jdk8源代码中,提交任务的执行逻辑部分如下所示:新增线程任务的时候代码:

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();//工作线程数小于核心线程的时候,可以填写worker线程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);}

通过深入阅读工作线程主要存放在了一个hashset集合当中, 添加工作线程部分的逻辑代码如下所示:

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 ||// 如果传入的core属性是false,则这里需要比对maximumPoolSize参数wc >= (core ? corePoolSize : maximumPoolSize))return false;//通过cas操作去增加线程池的工作线程数亩if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();  // Re-read ctlif (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 {//真正需要指定的任务是firstTask,它会被注入到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 startablethrow new IllegalThreadStateException();//workers是一个hashset集合,会往里面新增工作线程    workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {//worker本身是一个线程,但是worker对象内部还有一个线程的参数,//这个t才是真正的任务内容t.start();workerStarted = true;}}} finally {//如果worker线程创建好了,但是内部的真正任务还没有启动,此时突然整个//线程池的状态被关闭了,那么这时候workerStarted就会为false,然后将//工作线程的数目做自减调整。if (! workerStarted)addWorkerFailed(w);}return workerStarted;
}

进过理解之后,整体执行的逻辑以及先后顺序如下图所示:


首先判断线程池内部的现场是否都有任务需要执行。如果不是,则使用一个空闲的工作线程用于任务执行。否则会判断当前的堵塞队列是否已经满了,如果没有满则往队列里面投递任务,等待工作线程去处理。
如果堵塞队列已经满了,此时会判断工作线程数是否大于最大线程数,如果没有,则继续创建工作线程,如果已经达到则根据饱和策略去判断是果断抛出异常还是其他方式来进行处理。

线程池常用参数介绍

corePoolSize核心线程数,当往线程池内部提交任务的时候,线程池会创建一个线程来执行任务。即使此时有空闲的工作线程能够处理当前任务,只要总的工作线程数小于corePoolSize,也会创建新的工作线程。

maximumPoolSize当任务的堵塞队列满了之后,如果还有新的任务提交到线程池内部,此时倘若工作线程数小于maximumPoolSize,则会创建新的工作线程。

keepAliveTime上边我们说到了工作线程Worker(java.util.concurrent.ThreadPoolExecutor.Worker),当工作线程处于空闲状态中,如果超过了keepAliveTime依然没有任务,那么就会销毁当前工作线程。如果工作线程需要一直处于执行任务,每个任务的连续间隔都比较短,那么这个keepAliveTime 属性可以适当地调整大一些。

unitkeepAliveTime对应的时间单位

workQueue工作队列,当工作线程数达到了核心线程数,那么此时新来的线程就会被放入到工作队列中。线程池内部的工作队列全部都是继承自阻塞队列的接口,对于常用的阻塞队列类型为:

  • ArrayBlockingQueue

  • LinkedBlockingQueue

  • SynchronousQueue

  • PriorityBlockingQueue

RejectedExecutionHandlerJDK内部的线程拒绝策略包含了多种许多种,这里我罗列一些常见的拒绝策略给大家认识下:

  • AbortPolicy 直接抛出异常

  • CallerRunsPolicy 任务的执行由注入的线程自己执行

  • DiscardOldestPolicy 直接抛弃掉堵塞队列中队列头部的任务,然后执行尝试将当前任务提交到堵塞队列中。

  • DiscardPolicy 直接抛弃这个任务

从线程池设计中的一些启发

多消费队列的设计场景应用:业务上游提交任务,然后任务被放进一个堵塞队列中,接下来消费者需要从堵塞队列中提取元素,并且将它们转发到多个子队列中,各个子队列分别交给不同的子消费者处理数据。例如下图所示:

public interface AsyncHandlerService {/*** 任务放入队列中* * @param asyncHandlerData*/boolean putTask(AsyncHandlerData asyncHandlerData);/*** 启动消费*/void startJob();
}

多消费者分发处理实现类:

@Component("asyncMultiConsumerHandlerHandler")
public class AsyncMultiConsumerHandlerHandler implements AsyncHandlerService{private volatile TaskQueueHandler taskQueueHandler = new TaskQueueHandler(10);@Overridepublic boolean putTask(AsyncHandlerData asyncHandlerData) {return taskQueueHandler.addTask(asyncHandlerData);}@Overridepublic void startJob(){Thread thread = new Thread(taskQueueHandler);thread.setDaemon(true);thread.start();}/*** 将任务分发给各个子队列去处理*/static class TaskQueueHandler implements Runnable {private static BlockingQueue<AsyncHandlerData> tasks = new ArrayBlockingQueue<>(11);public static BlockingQueue<AsyncHandlerData> getAllTaskInfo() {return tasks;}private TaskDispatcherHandler[] taskDispatcherHandlers;private int childConsumerSize = 0;public TaskQueueHandler(int childConsumerSize) {this.childConsumerSize = childConsumerSize;taskDispatcherHandlers = new TaskDispatcherHandler[childConsumerSize];for (int i = 0; i < taskDispatcherHandlers.length; i++) {taskDispatcherHandlers[i] = new TaskDispatcherHandler(new ArrayBlockingQueue<>(100), "child-worker-" + i);Thread thread = new Thread(taskDispatcherHandlers[i]);thread.setDaemon(false);thread.setName("taskQueueHandler-child-"+i);thread.start();}}public boolean addTask(AsyncHandlerData asyncHandlerData) {return tasks.offer(asyncHandlerData);}@Overridepublic void run() {int index = 0;for (; ; ) {try {AsyncHandlerData asyncHandlerData = tasks.take();index = (index == taskDispatcherHandlers.length) ? 0 : index;taskDispatcherHandlers[index].addAsyncHandlerData(asyncHandlerData);index++;} catch (InterruptedException e) {e.printStackTrace();}}}}static class TaskDispatcherHandler implements Runnable {private BlockingQueue<AsyncHandlerData> subTaskQueue;private String childName;private AtomicLong taskCount = new AtomicLong(0);public TaskDispatcherHandler(BlockingQueue<AsyncHandlerData> blockingQueue, String childName) {this.subTaskQueue = blockingQueue;this.childName = childName;}public void addAsyncHandlerData(AsyncHandlerData asyncHandlerData) {subTaskQueue.add(asyncHandlerData);}@Overridepublic void run() {for (; ; ) {try {AsyncHandlerData asyncHandlerData = subTaskQueue.take();long count = taskCount.incrementAndGet();System.out.println("【" + childName + "】子任务队列处理:" + asyncHandlerData.getDataInfo() + count);Thread.sleep(3000);System.out.println("【" + childName + "】子任务队列处理:" + asyncHandlerData.getDataInfo()+" 任务处理结束" + count);} catch (Exception e) {e.printStackTrace();}}}}
}

测试接口:

@GetMapping(value = "/send-async-data")public boolean sendAsyncData(){AsyncHandlerData asyncHandlerData = new AsyncHandlerData();asyncHandlerData.setDataInfo("data info");boolean status = asyncMultiConsumerHandlerHandler.putTask(asyncHandlerData);if(!status){throw new RuntimeException("insert fail");}return status;}

这种设计模型适合用于对于请求吞吐量要求较高,每个请求都比较耗时的场景中。

自定义拒绝策略的应用根据具体的应用场景,通过实现java.util.concurrent.RejectedExecutionHandler接口,自定义拒绝策略,例如对于当抛出拒绝异常的时候,往数据库中记录一些信息或者日志。

相关案例代码:

public class MyRejectPolicy{static class MyTask implements Runnable{@Overridepublic void run() {System.out.println("this is test");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("任务被拒绝:" + r.toString());//记录一些信息}});for(int i=0;i<100;i++){Thread thread = new Thread(new MyTask());threadPoolExecutor.execute(thread);}Thread.yield();}
}

统计线程池的详细信息

通过阅读线程池的源代码之后,可以借助重写beforeExecute、afterExecute、terminated 方法去对线程池的每个线程耗时做统计。以及通过继承 ThreadPoolExecutor 对象之后,对当前线程池的coreSIze、maxiMumSize等等属性进行监控。

相关案例代码:

public class SysThreadPool extends ThreadPoolExecutor {private final ThreadLocal<Long> startTime = new ThreadLocal<>();private Logger logger = LoggerFactory.getLogger(SysThreadPool.class);public SysThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}public SysThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);}public SysThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);}public SysThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}@Overrideprotected void beforeExecute(Thread t, Runnable r) {super.beforeExecute(t, r);startTime.set(System.currentTimeMillis());}@Overrideprotected void afterExecute(Runnable r, Throwable t) {super.afterExecute(r, t);long endTime = System.currentTimeMillis();long executeTime = endTime - startTime.get();logger.info("Thread {}: ExecuteTime {}", r, executeTime);}@Overridepublic void shutdown() {super.shutdown();}@Overridepublic void execute(Runnable command) {super.execute(command);}public void getTaskInfo(){logger.info("coreSize: {}, maxSize: {}, activeCount:{},blockQueueSize:{}",super.getCorePoolSize(),super.getMaximumPoolSize(),super.getActiveCount(),super.getQueue().size());}static class MyTestTask implements Runnable{@Overridepublic void run() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {SysThreadPool sysThreadPool = new SysThreadPool(2,5,5000,TimeUnit.MILLISECONDS,new ArrayBlockingQueue(2));sysThreadPool.getTaskInfo();System.out.println("------------");for(int i=0;i<10;i++){Thread thread = new Thread(new MyTestTask());sysThreadPool.submit(thread);sysThreadPool.getTaskInfo();}System.out.println("------------");Thread.sleep(3000);}}

通过日志打印记录线程池的参数变化:

通过这份案例代码不妨可以设想下通过一些定时上报逻辑来实现线程池的监控功能。

推荐:

主流Java进阶技术(学习资料分享)

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

从实战到原理,线程池的各类使用场景整合相关推荐

  1. 37. 实战:Xpath+线程池实现抓取任意完整小说一千余节到本地txt文件/模板任意小说网站可套用(附源码)

    目录 前言 目的 思路 代码实现 1. 从给定的URL中拿到所有章节的标题和子链接 2. 将标题和子链接形成一个字典,便于后续提取信息 3. 实现在单个章节详细页面提取所有文本信息 4. 保存到本地文 ...

  2. 线程池的各种使用场景

    (1)高并发.任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换 (2)并发不高.任务执行时间长的业务要区分开看:        a)假如是业务时间长集中在IO操作上,也 ...

  3. 【Java线程】“打工人”初识线程池及自定义线程池实战

    目录 理论 原理 线程池创建 工作流程图 拒绝策略 参数设置 四种线程池 实战 理论 聊一下为什么要使用线程池? 程序的运行本质,就是通过使用系统资源(CPU.内存.网络.磁盘等等)来完成信息的处理, ...

  4. java线程池_Java 并发编程 线程池源码实战

    作者 | 马启航 杏仁后端工程师.「我头发还多,你们呢?」 一.概述 笔者在网上看了好多的关于线程池原理.源码分析相关的文章,但是说实话,没有一篇让我觉得读完之后豁然开朗,完完全全的明白线程池,要么写 ...

  5. 《C#多线程编程实战(原书第2版)》——3.6 在线程池中使用等待事件处理器及超时...

    本节书摘来自华章出版社<C#多线程编程实战(原书第2版)>一书中的第3章,第3.6节,作者(美)易格恩·阿格佛温(Eugene Agafonov),黄博文 黄辉兰 译,更多章节内容可以访问 ...

  6. python3 线程池源码解析_5分钟看懂系列:Python 线程池原理及实现

    概述 传统多线程方案会使用"即时创建, 即时销毁"的策略.尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器 ...

  7. 线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最大线程?线程池中线程复用原理

    1.一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务.阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使 ...

  8. Java-Java中的线程池原理分析及使用

    文章目录 概述 线程池的优点 线程池的实现原理 线程池的使用 创建线程池 向线程池中提交任务 关闭线程池 合理的配置线程池 线程池的监控 概述 我们在上篇博文 Java-多线程框架Executor解读 ...

  9. java线程池_Java多线程并发:线程基本方法+线程池原理+阻塞队列原理技术分享...

    线程基本方法有哪些? 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等. 线程等待(wait) 调用该方法的线程进入 WAITING 状态,只有等 ...

最新文章

  1. 官方iPhone SDK和开源工具链
  2. LNMP架构详解(1)——了解架构的基本信息
  3. python之禅中文-Python之禅与翻译之美
  4. 初探云原生应用管理(一): Helm 与 App Hub
  5. oracle 02085,OracleDBLink创建和维护以及ORA-02085解决办法
  6. JAVA四种遍历Map的方法
  7. pdf.js使用方法整理,web页面中pdf在线查看,web页面显示pdf文档
  8. 黑科技:Wi-Fi热点智能垃圾桶现身纽约
  9. Winform读报工具
  10. php单例模式深入讲解
  11. HIVE Sql 笛卡尔积关联导致查询过慢问题优化
  12. 智慧工地实名制劳务管理系统
  13. NTFS - 系统解析
  14. S32K144 EVB之FTM
  15. manjaro安装微信
  16. first season twenty-third episode,Ben was born!!!,Hi Ben???
  17. 备战蓝桥杯单片机倒数第四天 小蜜蜂老师公众号更新内容
  18. 【量化投资】高频交易(HFT),下一个风口?
  19. 无线充电仿真 simulink llc谐振器实现恒压输出 WPT
  20. NLP入门(五)用深度学习实现命名实体识别(NER)

热门文章

  1. 午睡还要被骂?新东方旗下公司被曝禁止午休引热议 CEO回应...
  2. 2999元起!苹果悄然发布两款iPad新品 升级至A12处理器
  3. iis5.1安装方法(适用于XP)以及运行调试asp程序,创建虚拟目录【整理】
  4. 安卓通过js与网页的H5页面进行交换
  5. 2018年高二计算机试题,2018年上学期高二信息技术期末考试试题.pdf
  6. oracle的日志模式,Oracle数据日志模式
  7. Android常见的内存泄漏分析
  8. 如何做一个国产数据库(二)
  9. datetime默认当前时间_简述Python培训之time和datetime的区别
  10. 【java】Thread.Sleep(0) 与 Thread.onSpinWait