线程池的状态?
回答:running->shutdown->stop->tidyng->TERMINATED
线程池状态怎么流转2.
回答:变成shutdown,执行shutdown()函数变成stop,执行shutdownnow函数
变成tining,所有任务已处理完
变成TERMINATED,线程池调结束
RUNNING | 接收新任务,处理队列中的任务 |
SHUTDOWN | 不接受新任务,但继续处理队列中已有的任务 |
STOP | 不接受新任务,不处理队列中的任务,中断正在执行的任务 |
TIDYING | 所有任务都已终止,工作线程数量为0,即将执行 terminated() 钩子方法 |
TERMINATED |
|
线程状态的流转3.
回答:新建状态(New)线程对象被创建后
阻塞状态(Blocked):等待监视器锁。
等待状态(Waiting)。等待另外一个线程的特定操作
超时等待(timed waiting)。等待特定的时间
运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
NEW:当前 Thread 对象虽然有了,但是内核的线程还没有(还没调用 start)
TERMINATED:当前 Thread 对象虽然还在,但是内核的线程已经销毁了(线程已经结束了)
RUNNABLE:就绪状态,正在 cpu 上运行 或 随时可以去 cpu 上运行
BLOCKED:因为 锁竞争 引起的阻塞
TIMED_WAITNG:有超时时间的等待,比如 sleep 或者 join 带参数版本
WAITING:没有超时时间的等待 join /wait
.讲-下Redis跳表的数据结构
// 简化版 C 结构体(来自 Redis 源码)
typedef struct zskiplistNode {sds ele; // 学生名字,比如 "xiaohong"double score; // 分数,比如 90.0struct zskiplistNode *backward; // ← 向后指针(用于倒着走)// 多层索引结构(每个节点有自己的层数)struct zskiplistLevel {struct zskiplistNode *forward; // → 向前指针unsigned int span; // 这一跳跨过了几个节点} level[];
} zskiplistNode;
ele | 存储成员名(Redis 用 sds 是它自己的字符串类型) |
score | 排序依据,按分数从小到大排 |
backward | 指向前一个节点,实现 ZREVRANGE 倒序遍历 |
level[].forward | 每一层的“快车道”指针 |
level[].span | 记录这一跳跳过了多少个底层节点(用于算排名) |
typedef struct zskiplist {struct zskiplistNode *header; // 头节点(不存真实数据)struct zskiplistNode *tail; // 尾节点(最后一个真实节点)unsigned long length; // 当前有多少个真实节点int level; // 所有节点中最高的层数
} zskiplist;
📌 想象这是一个“地铁控制中心”:
header
:总站,所有线路从这里出发tail
:终点站,方便快速找到最后一个人length
:当前排行榜上有多少人level
:目前最高有几条快车道(Level 0 ~ Level N)
IO多路复用
LINUX有哪些IO机制、select poll epoll,底层实现等等
BIO NIO等。
最基础的 TCP 的 Socket 编程,它是阻塞 1/0 模型,基本上只能一对一通信,那为了服务更多的客户端,我们需要改进网络 I/O 模型。
比较传统的方式是使用多进程/线程模型,每来一个客户端连接,就分配一个进程/线程,然后后续的读写都在对应的进程/线程,这种方式处理 100 个客户端没问题,但是当客户端增大到 10000 个时,10000 个进程/线程的调度、上下文切换以及它们占用的内存,都会成为瓶颈。
服务器的主进程负责监听客户的连接,一旦与客户端连接完成,accept0 函数就会返回一个「已连接Socket」,这时就通过 fork()函数创建一个子进程,实际上就把父进程所有相关的东西都复制一份,包括文件描述符、内存地址空间、程序计数器、执行的代码等。
当服务器与客户端 TCP 完成连接后,通过 pthread_create()函数创建线程,然后将「已连接 Socket」的文件描述符传递给线程函数,接着在线程里和客户端进行通信,从而达到并发处理的目的。我们可以使用线程池的方式来避免线程的频繁创建和销毁,所谓的线程池,就是提前创建若干个线程,这样当由新连接建立时,将这个已连接的 Socket 放入到一个队列里,然后线程池里的线程负责从队列中取出「已连接 Socket 」进行处理。
为了解决上面这个问题,就出现了 !/0 的多路复用,可以只在一个进程里处理多个文件的 10,linux 下有三种提供 /O 多路复用的 API,分别是:select、poll、epoll。
select 和 poll 并没有本质区别,
select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在Linux 系统中,由内核中的 FD SETSIZE 限制, 默认最大值为 1024 ,只能监听 0~1023 的文件描述符。
poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了select 的文件描述符个数限制,当然还会受到系统文件描述符限制。
它们内部都是使用「线性结构」来存储进程关注的 Socket 集合。
在使用的时候,首先需要把关注的 Socket 集合通过 select/poll 系统调用从用户态拷贝到内核态,然后由内核检测事件,当有网络事件产生时,内核需要遍历进程关注 Socket 集合,找到对应的 Socket,并设置其状态为可读/可写,然后把整个 Socket 集合从内核态拷贝到用户态,用户态还要继续遍历整个 Socket集合找到可读/可写的 Socket,然后对其处理。
很明显发现,select和 pol 的缺陷在于,当客户端越多,也就是 Socket 集合越大,Socket 集合的遍历和拷贝会带来很大的开销,因此也很难应对 C10K。
epoll 是解决 C10K 问题的利器,通过两个方面解决了 select/poll 的问题。
·epoll 在内核里使用「红黑树」来关注进程所有待检测的 Socket,红黑树是个高效的数据结构,增删改-般时间复杂度是 O(logn),通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个Socket 集合,减少了内核和用户空间大量的数据拷贝和内存分配。
epoll 使用事件驱动的机制,内核里维护了一个「链表」来记录就绪事件,只将有事件发生的 Socket 集合传递给应用程序,不需要像 select/poll 那样轮询扫描整个集合(包含有和无事件的 Socket),大大提高了检测的效率。
而且,epo 支持边缘触发和水平触发的方式,而 select/poll 只支持水平触发,一般而言,边缘触发的方式会比水平触发的效率高
这两个术语还挺抽象的,其实它们的区别还是很好理解的。
使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证-次性将内核缓冲区的数据读取完;
使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;
索引失效的场景有哪些,你知道什么改进方法吗
我们查询语句对索引字段进行左模糊匹配、表达式计算、函数、隐式类型转换操作,或者联合索引没有遵循最左匹配原则,这时候查询语句就无法走索引了
java版本改动问题 1.7 1.8有哪些主要区别
Java 7新特性:钻石操作符,try-with-resource 语句、支持动态类型语言、Fork/Join 框架等。
Java8新特性:Lambda 表达式、Stream API、新的Date/Time API、 NashornJavaScript 引|擎等。如何找到需要回收的垃圾、引用等等
- Java 8: 提供了Optional类来避免空指针异常,它是一个容器类,代表一个值存在或不存在的情况。
- Java 7: 需要手动检查对象是否为null,并进行相应的处理。