戳上方蓝字“可为编程”
点击右上角选择“设为星标”,好文不错过!
今日面试题:
工作中遇到的技术上的挑战和如何解决的?
Zookeeper发布订阅与注册中心是如何实现的?
MySql有哪些索引?有什么不同?
MYSQL使用什么数据结构,有哪些特点与优点?
讲一下双亲委派,Tomcat为啥要打破这个机制?
Redis为什么快?讲一下IO多路复用
CMS垃圾收集器是怎么回收的?
算法:实现两个线程分别打印奇数和偶数
关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!
MySql有哪些索引?有什么不同?
关注公众号【可为编程】回复【加群】进入面试技术交流群!!!
Mysql的索引主要取决于所对应的存储引擎,当采用innodb引擎主要分为两大类索引:
聚簇索引与非聚簇索引,MYisam存储引擎主要就是非聚簇索引。
聚簇索引:
聚簇索引指的就是数据与索引在一起,即数据是索引,索引即数据,InnoDB 索引和行数据均在mysql的bin目录的.idb文件中。在底层实现结构上的B+树的非叶子节点中保存下一指针和索引关键字值数据,在叶子节点中主要就是保存主键值和具体的数据,主键默认就是唯一的聚簇索引,查询的时候会根据ID直接获取到数据,一张表中只有一个聚簇索引但可以有多个非聚簇索引。
非聚簇索引:
非聚簇索引在innodb中也就是除了主键外的其他字段所创建的索引为非聚簇索引。非聚簇索引可以有多个,在查询时存在回表操作。先根据索引找到数据,数据中包含主键ID,如果查询是非索引字段数据,再根据索引记录的主键ID进行回表查询操作。在MyISAM存储引擎中,索引和数据分开存储,查询时根据索引找到对应的地址值,根据记录的地址url找到指定的数据。因此MyISAM存储引擎更适合查询量多的场景,不会二次回表操作,直接根据URL定位数据。物理上分别为.myi索引数据文件和.myd行数据文件这里的聚簇与非聚簇指的就是索引和数据是否在一起,innodb支持聚簇索引就是在一起,myisam数据与索引分开,不支持聚簇索引。
Zookeeper发布订阅与注册中心是如何实现的?
Zookeeper作为一个注册中心,首先它是我们分布式cap理论当中保障了C和P,也就是一致性和分区容错性。发布订阅采用了其节点特性,包括临时节点和持久节点,还有是有序临时节点,那么当我们每一个服务注册到zookeeper的时候,其实也就是在它下面创建一个节点,根据每一个服务创建一不同的一个节点来保障他和服务之间的关联关系。同时zk还可以监听各个节点的状态,动态监听节点的上下线,以及数据的发布和内容的分发。
发布订阅主要就是采用节点特性,同时也是其作为配置中心的一大特性。数据发布/订阅系统,即所谓的配置中⼼,顾名思义就是发布者发布数据供订阅者进⾏数据订阅。
目的
动态获取数据(配置信息)
实现数据(配置信息)的集中式管理和数据的动态更新
数据(配置信息)特性
(1)数据量通常⽐较⼩,每个节点中内存最大不能超过1M
(2)数据内容在运⾏时会发⽣动态更新
(3)集群中各机器共享,配置⼀致
如:机器列表信息、运⾏时开关配置、数据库配置信息等都可以作为节点内容存在node节点当中。
数据存储到节点:将数据(配置信息)存储到 Zookeeper 上的⼀个数据节点。
数据获取并设置监听器:应⽤在启动初始化节点从 Zookeeper 数据节点读取数据,并在该节点上注册⼀个数据变更Watcher。
数据变更与接收:当变更数据时,更新 Zookeeper 对应节点数据,Zookeeper会将数据变更通知发到各客户端,客户端接到通知后重新读取变更后的数据即可。
关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!
MYSQL使用什么数据结构,有哪些特点与优点?
MySQL主要采用B+树的结构来存储数据,特点与优点主要就是只B+树的特点和优点。
1、B+树行高均匀,比二叉树、二叉平衡树有更好的数据分布,不会产生树链表化转化为O(n)的现象,时间复杂度始终为Olog(n)。
2、每个非叶子节点不保存具体数据,只保存其左右两个节点范围的首个节点关键字值与下一个节点指针,叶子节点保存关键字、主键值和具体的数据内容以及回滚点roll_point和隐藏的事务trx_id。
3、叶子节点数据从左到右依次有序递增,更便于范围查询,有利于索引的最左匹配原则。
4、叶子节点之间采用双向链表相互关联,范围查询时效率更高。叶子节点内部采用链表存储数据。每个叶子节点相当于mysql的一页,因为取数据会采用就近原则,将附近可能需要的数据一并查出,每页大小为16KB。
还有哪些特点和优点欢迎补充!
讲一下双亲委派,Tomcat为啥要打破这个机制?
双亲委派就是在类加载阶段当一个类加载器(子加载器)接收到一个类加载的请求时,它不会直接加载这个类,而是首先将请求委派给其父类加载器。这个过程会一直持续到顶层的启动类加载器。如果父类加载器能够找到并加载该类,那么子加载器就不会再加载。只有在父类加载器无法加载该类时,子加载器才会尝试自己加载。
类加载器主要分为三个,由上到下为:BootStrapClassLoad、Extension ClassLoader、SystemClassLoad、ApplicationClassLoad,最底层为应用加载器。如果父类没有加载成功会继续返回给子类加载器进行加载,直到有加载器能加载为止。
双亲委派的目的就是要保护java中核心类库之间的安全性。BootStrapClassLoad启动类加载器,主要加载java核心类库,/jre/lib目录,出于安全考虑,它只加载包名为java、javax、sun等开头的类。
扩展类加载器加载/jre/lib/ext目录下的类
应用类加载器和系统加载器主要就是加载自己编写的java类,加载应用程序classpath目录下的类,也就是我们自己编写的Java类。
Tomcat打破双亲委派机制的原因主要是为了解决Web应用程序的类加载冲突问题,并提供更好的灵活性和可扩展性。
类加载冲突:在Web应用程序中,通常需要依赖一些共享的类库,这些类库可能由容器提供或者由应用程序自身提供。如果使用双亲委派模型,容器的类加载器会先尝试加载共享类库,这可能导致Web应用程序中提供的同名类无法被正确加载,从而产生类加载冲突。
限制灵活性:双亲委派模型要求类加载器在委派给父加载器之前尝试加载类,这可能会限制Web应用程序自定义类加载的灵活性。有些Web应用程序可能需要加载自定义的类或资源,而不希望受到容器类加载器的限制。
为了解决这些问题,Tomcat采用了自定义的Web应用程序类加载器,它打破了双亲委派模型的一部分规则。具体来说,Tomcat的类加载器首先会查找Web应用程序内部的类和资源,而不是立即委派给父类加载器。这样可以确保Web应用程序中提供的类能够被正确加载,避免类加载冲突。同时,对于Java核心类库和Java EE API类,Tomcat的类加载器仍然会遵循双亲委派模型,以确保这些类的稳定性和正确性。
关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!
Redis为什么快?讲一下IO多路复用
首先说一下redis为什么快?
1、内部采用多线程处理IO请求,单线程执行命令,会在内部采用单线程排队执行命令,避免了多线程的开销和cpu竟态切换。
2、基于内存操作,效率高。数据采用K-V结构存储,时间复杂度为O(1)。
3、同时采用IO多路复用技术,提高IO并发请求。
4、C语⾔实现,优化过的数据结构,基于⼏种基础的数据结构。
但有的人说redis不只是单线程,也用到了多线程,那到底是哪里用到了呢?
Redis6.0的多线程是⽤多线程来处理数据的读写和协议解析,还有就是数据的持久化以及复制但是Redis执⾏命令还是单线程的。Redis基于Reactor模式(底层是IO多路复用,可以理解为事件分发)开发了网络事件处理器,这个处理器被称为文件事件处理器。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。因此我们在执行的过程中尽量少使用keys、flushall、flushdb等命令,因为会进行长时间的扫描造成请求阻塞。事件会根据文件事件分派器分配给各个事件处理器,不同的事件对应不同的事件处理器来处理。
IO多路复用
IO指的就是IO通信,客户端请求和Redis响应,多路就是指多个Socket网络连接链路,也可能是channel、TCP等。复用指的就是单线程通道复用一个,举个例子类似于课堂上老师在让全班学生做一道题,同步执行就是学生做完一道题老师就批改一道,其他做完的同学就等待,这种就是阻塞式IO操作。IO多路复用就是有学生做完就去找老师进行批改,学生不用等待。IO多路复用技术允许单个线程同时处理多个I/O操作,通过监听多个文件描述符Socket网络连接的状态变化,来复用单个线程处理多个网络连接。
关注公众号【可为编程】回复【加群】进入面试技术交流群!!!
Redis基于Reactor模式(底层是IO多路复用,可以理解为事件分发)开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个Socket套接字(文件句柄)、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。文件事件是对套接字操作的抽象,每当一个套接字准备好执行连接应答(accept)、写入(write)、读取(read)、关闭(close)等操作时,就会产生一个文件事件。
Redis单线程模型利用操作系统提供的I/O多路复用技术,比如常见的select、poll、epoll等,Redis使用了I/O多路复用技术,可以选择select、poll、epoll等机制来实现。具体选择哪种机制,取决于Redis所在的操作系统和配置。在Linux系统上,Redis默认使用epoll机制,因为它在处理大量并发连接时效率更高。而在其他不支持epoll的系统上,Redis会选择select或poll机制。
来监听多个网络套接字(socket)的状态变化。当这些套接字中的某一个或者某几个状态发生变化(比如可读AE_READABLE或者可写AE_WRITABLE)时,操作系统会通知Redis进行处理,就会将其加入到事件队列中,并调用相应的事件处理器进行处理。事件循环是一个无限循环,只有在服务器关闭时才会退出。事件处理器是处理具体事件的逻辑部分,它会根据事件的类型进行相应的处理。比如,当某个客户端连接到来时,文件事件处理器会负责接收连接,并创建一个新的套接字描述符与之通信;当某个客户端发送命令请求时,事件处理器会负责读取请求,解析命令,并执行相应的操作;当需要将结果返回给客户端时,事件处理器会负责将结果写入到相应的套接字描述符中。这样Redis就可以在一个线程中高效地处理多个网络连接,而不需要为每个连接都分配一个线程。这就是Redis能够使用单线程模型来处理大量并发连接的关键所在。
CMS垃圾收集器是怎么回收的?
CMS是java1.5版本之前采用的,主要采用分代年龄回收和并发收集回收,第一次实现了让垃圾收集线程与用户线程同时工作。
CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,CMS 的垃圾收集算法采用标记-清除算法,并且也会”Stop-the-World”。
主要分为三个部分,第一部分为初始标记、第二部分为并发标记、第三部分为重新标记、第四部分为并发清理。
初始标记时会发生短暂性的STW,这个阶段的主要任务仅仅只是标记出 GCRoots 能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快。
并发标记(Concurrent-Mark)阶段:从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程或用户等工作线程一起并发运行。
重新标记(Remark)阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间STW通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。
并发清除(Concurrent-Sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
尽管 CMS 收集器采用的是并发回收(非独占式),但是在其初始化标记和再次标记这两个阶段中仍然需要执行“Stop-the-World”机制暂停程序中的工作线程,不过暂停时间并不会太长,因此可以说明目前所有的垃圾收集器都做不到完全不需要“stop-the-World”,只是尽可能地缩短暂停时间。
由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的。
CMS 收集器的垃圾收集算法采用的是标记清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象所占用的内存空间极有可能是不连续的一些内存块,不可避免地将会产生一些内存碎片。这也是它的最大的缺点。
有人会觉得既然 Mark Sweep 会造成内存碎片,那么为什么不把算法换成 Mark Compact?
答案其实很简单,因为当并发清除的时候,用 Compact 整理内存的话,原来的用户线程使用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact 更适合“Stop the World” 这种场景下使用。
算法-实现两个线程分别打印奇数和偶数
public class TwoThreadPrintOldAndEven {
private static int count;
private static final Object object = new Object();
ReentrantLock lock = new ReentrantLock();
class threadObject implements Runnable {
@Override
public void run() {
while (count < 100) {
try {
if (lock.tryLock()) {
//奇数
if ((count % 2) == 1) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
} finally {
lock.unlock();
}
}
}
}
class threadObject1 implements Runnable {
@Override
public void run() {
while (count < 100) {
try {
if (lock.tryLock()) {
//偶数
if ((count % 2) == 0) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
} finally {
lock.unlock();
}
}
}
}
public static void main(String[] args) throws Exception {
new Thread(new TwoThreadPrintOldAndEven().new threadObject(), "打印奇数").start();
new Thread(new TwoThreadPrintOldAndEven().new threadObject1(), "打印偶数").start();
// new Thread(new Runnable() {
// @Override
// public void run() {
// while (count < 100) {
// if ((count % 2) == 0) {
// System.out.println(Thread.currentThread().getName() + ":" + count++);
// }
// }
// }
// }, "打印偶数").start();
// new Thread(new Runnable() {
// @Override
// public void run() {
// while (count < 100) {
// synchronized (object) {
求奇偶数还可以按照这种 直接&运算 if((count&1)==1){
// if ((count % 2) == 1) {
// System.out.println(Thread.currentThread().getName() + ":" + count++);
// }
// }
// }
// }
// }, "打印奇数").start();
}
}
关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!
END
可为编程
知足知不足 有为有不为 为与不为皆为可为
长按指纹 >识别图中二维码 >添加关注
论走多远 都不要忘了当初为什么出发