首页 文章 精选 留言 我的

精选列表

搜索[Excel导题],共7164篇文章
优秀的个人博客,低调大师

大厂面试真题:“JDK 线程池”如何保证“核心线程”不被销毁?

2 --> 前言 很早之前那个时候练习线程池, 就是感觉线程池类似于 ArrayList 这种集合类结构, 将 Thread类存储, 来任务了就进行消费, 然鹅... 线程包装类 线程池并不是对 Thread 直接存储, 而是对 Thread 进行了一层包装, 包装类叫做 Worker 线程在线程池中的存储结构如下: private final HashSet<Worker> workers = new HashSet<Worker>(); 先看一下 Worker 类中的变量及方法 private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * 此线程为线程池中的工作线程 */ final Thread thread; /** * 指定线程运行的第一项任务 * 第一项任务没有则为空 */ Worker(Runnable firstTask) { ... this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } /** * 运行传入的 Runnable 任务 */ @Override public void run() { runWorker(this); } } 通过 Worker 的构造方法和重写的 run 得知:线程池提交的任务, 会由 Worker 中的 thread 进行执行调用 addWorker 这里还是要先放一下线程池的执行流程代码, 具体流程如下: public void execute(Runnable command) { ... 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); } 我们具体看一下 ThreadPoolExecutor#addWorker private boolean addWorker(Runnable firstTask, boolean core) { ... // worker运行标识 boolean workerStarted = false; // worker添加标识 boolean workerAdded = false; Worker w = null; try { // 调用Worker构造方法 w = new Worker(firstTask); // 获取Worker中工作线程 final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) throw new IllegalThreadStateException(); // 如无异常, 将w添加至workers workers.add(w); int s = workers.size(); if (s > largestPoolSize) // 更新池内最大线程 largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { // 启动Woker中thread工作线程 t.start(); // 设置启动成功标识成功 workerStarted = true; } } } finally { // 如果启动失败抛出异常终止, 将Worker从workers中移除 if (!workerStarted) addWorkerFailed(w); } return workerStarted; } 这里需要着重关心下 t.start(), t 是我们 Worker 中的工作线程 上文说到 Worker 实现了 Runnable 接口, 并重写了 run 方法, 所以 t.start() 最终还是会调用 Worker 中的 run() @Override public void run() { runWorker(this); } runWorker ThreadPoolExecutor#runWorker 是具体执行线程池提交任务的方法, 大致思路如下: 1、获取 Worker 中的第一个任务 2、如果第一个任务不为空则执行具体流程 3、第一个任务为空则从阻塞队列中获取任务, 这一点也是核心线程不被回收的关键 runWorker() 中有两个扩展方法, beforeExecute、afterExecute, 在任务执行前后输出一些重要信息, 可用作与监控等... final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { // 【重点】getTask() 是核心线程不被回收的精髓 while (task != null || (task = getTask()) != null) { w.lock(); ... try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } ... } finally { afterExecute(task, thrown); } } finally { // 【重点】执行完任务后, 将Task置空 task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { // 退出Worker processWorkerExit(w, completedAbruptly); } } 关键关注下 while 循环, 内部会在执行完流程后将 task 设置为空, 这样就会跳出循环 可以看到 processWorkerExit 是在 finally 语句块中, 相当于 获取不到阻塞队列任务就会去关闭 Worker 线程池是如何保证核心线程获取不到任务时不被销毁呢? 我们继续看一下 getTask() 中是如何获取任务 getTask ThreadPoolExecutor#getTask 只做了一件事情, 就是从线程池的阻塞队列中获取任务返回 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); boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } } timed 重点代码从上面截出来, 首先是判断是否需要获取阻塞队列任务时在规定时间返回 /** * 【重点】判断线程是否超时, 这里会针对两种情况判断 * 1. 设置allowCoreThreadTimeOut参数默认false * 如果为true表示核心线程也会进行超时回收 * 2. 判断当前线程池的数量是否大于核心线程数 * * 这里参与了或运算符, 只要其中一个判断符合即为True */ boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; 根据 timed 属性, 判断获取阻塞队列中任务的方式 /** * 【重点】根据timed判断两种不同方式的任务获取 * 1. 如果为True, 表示线程会根据规定时间调用阻塞队列任务 * 2. 如果为False, 表示线程会进行阻塞调用 */ Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); 这里就比较清楚了, 如果 timed 为 True, 线程经过 非核心线程过期时间后还没有获取到任务, 则方法结束, 后续会将 Worker 进行回收 如果没有设置 allowCoreThreadTimeOut 为 True, 以及当前线程池内线程数量不大于核心线程 那么从阻塞队列获取的话是 take(), take() 会 一直阻塞, 等待任务的添加返回 这样也就间接达到了核心线程数不会被回收的效果 getTask流程 “图片本来是要放上面的, 但是画的实在有点不忍直视 ️” 核心线程与非核心线程区别 核心线程只是一个叫法, 核心线程与非核心线程的区别是: 创建核心线程时会携带一个任务, 而非核心线程没有 如果核心线程执行完第一个任务, 线程池内线程无区别 线程池是期望达到 corePoolSize 的并发状态, 不关心最先添加到线程池的核心线程是否会被销毁

优秀的个人博客,低调大师

Netty面试常驻题:你知道Netty的零拷贝机制吗?

理解零拷贝 零拷贝是Netty的重要特性之一,而究竟什么是零拷贝呢?WIKI中对其有如下定义: "Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. 从WIKI的定义中,我们看到“零拷贝”是指计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。 Non-Zero Copy方式: Zero Copy方式: 从上图中可以清楚的看到,Zero Copy的模式中,避免了数据在用户空间和内存空间之间的拷贝,从而提高了系统的整体性能。Linux中的sendfile()以及Java NIO中的FileChannel.transferTo()方法都实现了零拷贝的功能,而在Netty中也通过在FileRegion中包装了NIO的FileChannel.transferTo()方法实现了零拷贝。 而在Netty中还有另一种形式的零拷贝,即Netty允许我们将多段数据合并为一整段虚拟数据供用户使用,而过程中不需要对数据进行拷贝操作,这也是我们今天要讲的重点。我们都知道在stream-based transport(如TCP/IP)的传输过程中,数据包有可能会被重新封装在不同的数据包中,例如当你发送如下数据时: 有可能实际收到的数据如下: 因此在实际应用中,很有可能一条完整的消息被分割为多个数据包进行网络传输,而单个的数据包对你而言是没有意义的,只有当这些数据包组成一条完整的消息时你才能做出正确的处理,而Netty可以通过零拷贝的方式将这些数据包组合成一条完整的消息供你来使用。而此时,零拷贝的作用范围仅在用户空间中。 以Netty 3.8.0.Final的源代码来进行说明 ###ChannelBuffer接口 Netty为需要传输的数据制定了统一的ChannelBuffer接口。该接口的主要设计思路如下: 1.使用getByte(int index)方法来实现随机访问 2.使用双指针的方式实现顺序访问 每个Buffer都有一个读指针(readIndex)和写指针(writeIndex) 在读取数据时读指针后移,在写入数据时写指针后移 定义了统一的接口之后,就是来做各种实现了。Netty主要实现了HeapChannelBuffer,ByteBufferBackedChannelBuffer等等,下面我们就来讲讲与Zero Copy直接相关的CompositeChannelBuffer类。###CompositeChannelBuffer类 CompositeChannelBuffer类的作用是将多个ChannelBuffer组成一个虚拟的ChannelBuffer来进行操作。 为什么说是虚拟的呢,因为CompositeChannelBuffer并没有将多个ChannelBuffer真正的组合起来,而只是保存了他们的引用,这样就避免了数据的拷贝,实现了Zero Copy。下面我们来看看具体的代码实现,首先是成员变量 private int readerIndex; private int writerIndex; private ChannelBuffer[] components; private int[] indices; private int lastAccessedComponentId; 以上这里列出了几个比较重要的成员变量。其中readerIndex既读指针和writerIndex既写指针是从AbstractChannelBuffer继承而来的;然后components是一个ChannelBuffer的数组,他保存了组成这个虚拟Buffer的所有子Buffer,indices是一个int类型的数组,它保存的是各个Buffer的索引值;最后的lastAccessedComponentId是一个int值,它记录了最后一次访问时的子Buffer ID。 从这个数据结构,我们不难发现所谓的CompositeChannelBuffer实际上就是将一系列的Buffer通过数组保存起来,然后实现了ChannelBuffer 的接口,使得在上层看来,操作这些Buffer就像是操作一个单独的Buffer一样。 创建 接下来,我们再看一下CompositeChannelBuffer.setComponents方法,它会在初始化CompositeChannelBuffer时被调用。 /** * Setup this ChannelBuffer from the list */ private void setComponents(List<ChannelBuffer> newComponents) { assert !newComponents.isEmpty(); // Clear the cache. lastAccessedComponentId = 0; // Build the component array. components = new ChannelBuffer[newComponents.size()]; for (int i = 0; i < components.length; i ++) { ChannelBuffer c = newComponents.get(i); if (c.order() != order()) { throw new IllegalArgumentException( "All buffers must have the same endianness."); } assert c.readerIndex() == 0; assert c.writerIndex() == c.capacity(); components[i] = c; } // Build the component lookup table. indices = new int[components.length + 1]; indices[0] = 0; for (int i = 1; i <= components.length; i ++) { indices[i] = indices[i - 1] + components[i - 1].capacity(); } // Reset the indexes. setIndex(0, capacity()); } 通过代码可以看到该方法的功能就是将一个ChannelBuffer的List给组合起来。它首先将List中得元素放入到components数组中,然后创建indices用于数据的查找,最后使用setIndex来重置指针。这里需要注意的是setIndex(0, capacity())会将读指针设置为0,写指针设置为当前Buffer的长度,这也就是前面需要做assert c.readerIndex() == 0和assert c.writerIndex() == c.capacity()这两个判断的原因,否则很容易会造成数据重复读写的问题。 所以Netty推荐我们使用ChannelBuffers.wrappedBuffer方法来进行Buffer的合并,因为在该方法中Netty会通过slice()方法来确保构建CompositeChannelBuffer是传入的所有子Buffer都是符合要求的。 数据访问 CompositeChannelBuffer.getByte(int index)的实现如下: public byte getByte(int index) { int componentId = componentId(index); return components[componentId].getByte(index - indices[componentId]); } 从代码我们可以看到,在随机查找时会首先通过index获取这个字节所在的componentId既字节所在的子Buffer序列,然后通过index - indices[componentId]计算出它在这个子Buffer中的第几个字节,然后返回结果。 下面再来看一下componentId(int index) 的实现: private int componentId(int index) { int lastComponentId = lastAccessedComponentId; if (index >= indices[lastComponentId]) { if (index < indices[lastComponentId + 1]) { return lastComponentId; } // Search right for (int i = lastComponentId + 1; i < components.length; i ++) { if (index < indices[i + 1]) { lastAccessedComponentId = i; return i; } } } else { // Search left for (int i = lastComponentId - 1; i >= 0; i --) { if (index >= indices[i]) { lastAccessedComponentId = i; return i; } } } throw new IndexOutOfBoundsException("Invalid index: " + index + ", maximum: " + indices.length); } 从代码中我们发现,Netty以lastComponentId既上次访问的子Buffer序号为中心,向左右两边进行搜索,这样做的目的是,当我们两次随机查找的字符序列相近时(大部分情况下都是这样),可以最快的搜索到目标索引的componentId。 写在最后 欢迎大家关注我的公众号【风平浪静如码】,海量Java相关文章,学习资料都会在里面更新,整理的资料也会放在里面。 觉得写的还不错的就点个赞,加个关注呗!点关注,不迷路,持续更新!!!

优秀的个人博客,低调大师

Arichmetic-算法解析和面试经常出的算法题大总结

ylbtech-Arichmetic:Arichmetic-算法 去一些知名企业面试,算总往往是其考察的重点。在我最初求职当中,常常吃算法的亏,真的是叫苦不堪。终于在N次失败后,痛定思痛决定要学好算法。于是开始买书,查资料,求人解析,真的是苍天不负有心人,也算是学有小成。写此博文一是做个终结,迈过初级算法员,向中级算法员挺进;二是希望能结识更多热爱算法的童鞋,相互提携,一起进步。 1.A,1-10 算法理论 程序的三种基本控制架构【只有提纲】 流程图【只有提纲】 Console-算法[for]-简单画图 Console-算法[for]-高斯数 ,6 Console-算法[for]-国王与老人的六十四格 Console-算法[for]-素数 Console-算法-continue与break的区别? Logic-算法-(狼-羊-白菜) Logic-算法-分金条 1.B,11-20 ,11 Logic-算法-三个灯泡与三个开关 Logic-算法-八个箱子找一个最轻的 Console-算法[for,if,break]-五个好朋友分苹果 Console-算法[for,if]-一堆桃子和一只猴子 Console-算法[for,if]-(大马-小马-马驹托砖) ,16 Logic-算法-XX部队XX侦察队员 Logic-算法-两根粗细不均匀的绳子去标记45分钟 Console-算法[while,if]-一张纸折几折会超过世界最高峰 ylb-算法-算法总结 Console-算法-递归算法示例 1.C,21-22 ,21 Concept-算法-闰年平年概念简介 Console-算法-冒泡排序法|倒水法 本文转自ylbtech博客园博客,原文链接:http://www.cnblogs.com/ylbtech/archive/2013/02/27/2934709.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

CCAH-CCA-500-1题:What is the maximum amount of virtual memory allocated...

1. What is the maximum amount of virtual memory allocated for each map task before YARN will kill its Container? Your cluster’s mapred-start.xml includes the following parameters 点击(此处)折叠或打开 <name>mapreduce.map.memory.mb</name> <value>4096</value> <name>mapreduce.reduce.memory.mb</name> <value>8192</value> And any cluster’s yarn-site.xml includes the following parameters <name>yarn.nodemanager.vmen-pmen-ration</name> <value>2.1</value> What is the maximum amount of virtual memory allocated for each map task before YARN will killits Container? A. 4 GB B. 17.2 GB C. 8.9GB D. 8.2 GB E. 24.6 GB 问题: 每个 map任务,在运行时,虚拟内存能够分配到多大时,就会被 Yarn资源管理器kill掉? 解析: 参数yarn.nodemanager.vmen-pmen-ration : 是指 每单位的物理内存总量对应的虚拟内存量,默认是2.1,表示每使用1MB的物理内存,最多可以使用2.1MB的虚拟内存总量。 所以map memory 当前设置是4096Mb ,那么vm的最大分配为(4096Mb *2.1)/1024Mb =8.4G,那么接近答案的是D.

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册