HashMap和ConcurrentHashMap都是存储键值对的数据结构,不同的是HashMap是线程不安全的,ConcurrentHashMap是线程安全的,HashMap在高并发情况下会出现数据不一致问题。
HashMap实现原理:
1.7 基于数组和链表 ,将数据封装成一个Entry节点 ,通过哈希算法找到节点插入的位置,通过头插法进行插入。当hashMap元素数量到达16*0.75时会将容量翻倍进行扩容操作,会重新计算节点的位置并进行插入。但是使用头插法插入在并发扩容时会出现死链问题。具体在于线程T1,T2同时对一个节点进行操作,该节点有 a,b,c。这时A->B->C 若这时线程T2进行扩容, 线程T1休眠,扩容完成后的指针指向变化对T1不可知,会导致死循环 。
1.8 基于数组和链表和红黑树。在1.7中当链表长度太长,向下查找会影响查找效率。所以在1.8中当链表长度超过8后会进行树化,降低链表的高度。当链表长度超过8后先通过对数组扩容减少链表的长度,当数组长度超过64后会进行树化,树化是一个小概率事件。在节点插入时是通过尾插法,可以方便计算链表的长度的同时也避免了死链问题。
ConcurrentHashMap实现原理;
1.7 在jdk1.7中ConcurrentHashMap被称作分段锁,它由多个Segment组成,每个Segment是一个独立的哈希表。维护一个数组节点。每个Segment维护了一个独立的锁。Segment 继承 ReentrantLock 加锁。Segment默认值为16,也就是说并发度为16.其数组在初始化后被固定不可扩容 。
1.8 在jdk1.8中ConcurrrentHashMap引入了红黑树,同时使用Cas操作来替换分段锁,提供更好的并发和可拓展性。结构为数组+链表+红黑树。在树的内部节点node 也是通过volatile来修饰保证可见性。通过cas保证原子性,它可以被扩容,初始为16 .
红黑树是一种能够实现自平衡的二叉搜索树,他在每个节点上增加了一个位来表述节点的颜色红色或黑色,他的查找,删除,增加时间复杂度为o(logn).
允许局部不平衡的设计有以下几个原因:
Tcp 是一种面向连接的,可靠的,基于字节流的传输层通信协议
Udp是无连接的用户数据报协议 。
区别:
1 Tcp 面向连接 需要三次握手建立连接,四次挥手释放连接Udp是无连接的,不需要建立连接就可以发送数据
2 Tcp是可靠的通信,Tcp会有超时重传,数据校验,拥塞和流量控制,确认机制等保证连接的可靠性,Udp无需连接没有这些措施,将以最大的速度进行传播,不保证数据的可靠性
3 Tcp面向字节流 ,Udp面向报文
4 Tcp是点对点的 Udp 可以一对一 一对多 多对一
1 域名解析,通过找浏览器Dns缓存,本地host文件 域名服务器查找等找到该域名对应的ip地址
2 通过ip地址使用ARP地址解析协议找到对应的服务器
3 发起三次握手连接
4 服务器响应请求
5 浏览器解析相应的数据
7 对数据进行渲染
8 四次挥手断开连接
三次握手就是在建立一个Tcp连接时,需要客户端和服务端发送三个包,来保证双方正常的通信能力,并指定初始化序列号为后续的可靠传输做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。
为什么一定要三次握手而不能是两次握手
如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。
Mysql事务是数据库的最小工作单元,是一组不可再分的操作集合
四大特性 ACID 原子性,一致性,隔离性,持续性
隔离级别:可重复读,读未提交,不可重复读,串行化
ConcurrentHashMap 和 Hashtable都是并发安全的两个类,不同点在于 1 ConcurrentHashMap的并发度更高,无论在1.7中并发度16还是再1.8中使用cas达到更高的并发度都比Hashtable的并发度1高。Hashtable使用的是Synchronized实现全局锁,性能低 。 在扩容时Hashtable需要锁表。ConcrrentHashmap不需要
Spring IOC 控制反转,将对象的创建由程序交由给spring生成Bean . 它将对象的创建和依赖关系的管理交给容器(也就是Spring框架),而不是由应用程序代码直接控制。通过IOC容器,我们只需要在配置文件或注解中声明依赖关系,容器会自动根据配置创建并注入相关的对象。这样可以实现对象之间的解耦,提高代码的灵活性、可维护性和可测试性。
Aop , 面向切面编程,在不更改程序的情况下,通过切面来实现对程序的功能增强,AOP就是把一个业务逻辑功能抽取出来,然后动态把这个功能切入到需要的方法(或行为)中,需要的才切入,这样便于减少系统的重复代码, 降低模块间的耦合度。
四种常用的Executor自带的线程池 ,分别是 newCachedThreadPool , newFixThreadPool , newSingleThreadPool,new SchduledThreadPool . 分别代表 可缓存线程池 , 定长线程池 , 单线程池, 周期性线程池 。
在newCachedThreadPool中 ,队列使用的是没有容量限制的SynchronousQueue,适合短期异步任务,可以直接复用已有的线程。在newFIxThreadPool和newSingleThreadPool中使用的是LinkedBlockingQueue基于链表的无界队列,无限提交任务会导致oom , 在newSchduledThreadPool中使用的是DelayedWorkQueue延迟阻塞队列,队列满自动扩容,所以无需设置最大参数。它是基于堆实现优先级,适用于执行延时或周期性任务。
Runnable和Thread是创建线程的两种方式 ,但他们也有一点创建上的区别,Runnable是接口,创建线程只需要实现接口run方法,Thread创建线程需要继承Thread类,这也导致了他们的使用情况不一样,一般来讲如果希望任务类实现其他接口或继承其他类时我们可以选择实现Runnable接口以免避免单继承的限制,如果由多个线程需要共享一个资源时,可以将该资源封装在runnable里面达到公用。如果希望对线程类进行更多的拓展时可以使用Thread来创建线程。
Runnable和Callable的区别
1 返回值 Runnable没有返回值,Callable可以有返回值
2 因为Runnable没有返回值所以不能抛出异常,Callable可以抛出异常
3 异步执行:Runnable只能通过Thread来异步执行,Callable可以使用Future来获取异步执行的结果
在线程池中的异常取决于任务的提交方式,当任务是以submit提交时要想获取异常必须通过Get方法获取一个Future对象的返回值,如果任务是以execute提交的话,直接抛出异常或者可以通过tryCatch捕获
对于单个线程而言,Thread方法中提供了setUncaughtExceptionHandler方法可以抛出线程内的异常,不使用这个方法异常会被吃掉。
副线程可以捕获自身的异常.
其实就是synchronized和Lock的区别
JVM 的对象分配在 Java 堆(Heap)中,而 Class 对象则是分配在方法区(Method Area)。
## 1 常用的设计模式介绍:单例模式、装饰者模式等
以下是一些常见的设计模式例子:
是的,Java 程序也可能出现内存溢出(Out of Memory)的情况。以下是一些常见的情况导致内存溢出的情况:
3.1 双亲委派模型指的是我们程序的泪在进行类加载时先由应用程序加载器向上提交,交给拓展类加载器再交给启动类加载器进行加载。如果所有的父类加载器都无法加载这类则会交由子类加载器去加载。
3.2 这种双亲委派的过程可以保证类加载的一致性和唯一性。因为每个类加载器在自己的命名空间中搜索类,避免了类的重复加载,同时也保证了核心类不会被用户自定义的类加载器替换或篡改。这种方式也使得 Java 中的类隔离和模块化成为可能,提供了良好的安全性和可扩展性。
在minor gc后年龄到达阈值15后会被放入老年代,还有如果是大对象也会直接放入老年代。
快速排序说一下过程
Spring AOP(面向切面编程)的实现原理主要基于动态代理和反射机制。Spring AOP 提供了一种在运行时动态地将切面织入到目标对象方法的机制,以实现横切关注点的功能。
在 Spring AOP 中,主要有两种代理方式:基于接口的 JDK 动态代理和基于类的 CGLIB 动态代理。
基于接口的 JDK 动态代理:如果目标对象实现了接口,Spring 使用 JDK 提供的 java.lang.reflect.Proxy 类来创建代理对象。当目标对象方法被调用时,代理对象会通过反射机制拦截方法的执行,并在方法的前后织入切面逻辑。
基于类的 CGLIB 动态代理:如果目标对象没有实现接口,Spring 会使用 CGLIB(Code Generation Library)动态生成一个继承自目标类的子类,在子类中拦截目标方法的执行并织入切面逻辑。CGLIB 是一个强大的第三方库,通过修改字节码生成子类来实现代理。
Spring AOP 通过 AspectJ 注解或 XML 配置将切面逻辑定义为切面,切面中的切点定义了哪些目标方法会被拦截,并在切点上定义通知(advice),如前置通知、后置通知、异常通知等。在运行时,Spring AOP 在目标对象上动态创建代理对象,代理对象拦截被切点匹配的方法,并执行相应的通知代码。
总结起来,Spring AOP 的实现原理就是通过动态代理和反射机制,在运行时动态生成代理对象,并在代理对象中拦截目标方法的执行,实现对切面逻辑的织入。这样可以将公共的横切关注点与业务逻辑相分离,达到了解耦和代码复用的效果。
BIO(Blocking I/O)和 NIO(Non-blocking I/O)是 Java 中用于处理 I/O 操作的两种不同的编程模型。
BIO(阻塞 I/O)模型是最原始的 I/O 编程模型,它以阻塞方式进行 I/O 操作。在 BIO 中,每个 I/O 操作(如读写操作)都会阻塞当前线程,直到操作完成才返回。这意味着每个连接都需要独立的线程进行处理,因此在高并发的环境中,BIO 性能不佳。
NIO(非阻塞 I/O)模型是在 JAVA1.4 中引入的,在 NIO 中,仅当数据准备好时,才会执行 I/O 操作。NIO 使用了非阻塞的 Channel 和 Selector,可以在一个线程中管理多个连接,因此在高并发的场景下,NIO 可以更好地处理大量的连接。
在实现上,BIO 使用了传统的 InputStream 和 OutputStream 来进行 I/O 操作,通常是基于字节流(Byte Stream)来进行读写。而 NIO 使用了 Channel 和 Buffer 来进行 I/O 操作,提供了更灵活和高效的读写方式。
BIO 的典型示例是使用 Socket 进行网络通信,在服务器端,每个客户端连接都需要一个独立的线程来处理;在客户端,将阻塞等待从服务器接收数据。
NIO 则是基于事件驱动的,使用 Selector 来管理多个 Channel,并通过事件通知机制来处理 I/O 操作。NIO 可以通过一个线程管理多个连接,当连接有数据可读时,通过事件通知机制进行处理。
总结来说,BIO 是单线程处理单个连接的阻塞模型,适合连接数较少的场景;而 NIO 是使用 Selector 多路复用模型,适合高并发的场景。
JVM(Java Virtual Machine)的垃圾回收(Garbage Collection)算法是用于自动管理内存的一种机制,用于回收不再被程序使用的对象所占用的内存空间。
JVM 垃圾回收算法主要有以下几种:
标记-清除算法(Mark and Sweep):这是最基本的垃圾回收算法。首先,它通过一个根对象(如 GC Roots)开始,递归地遍历所有可达对象,并在遍历过程中将这些对象进行标记。然后,遍历整个堆,清除未被标记的对象,并使这些内存空间重新可用。
复制算法(Copying):将堆内存空间分为两部分,每次只使用其中一部分。当当前使用的部分满了之后,将存活的对象复制到另一部分,并且顺序排列,之后清除当前部分的所有对象。这种算法适用于对象存活率较低的场景,不会造成太多空间浪费。
标记-整理算法(Mark and Compact):结合了标记-清除和复制两种算法的优点。首先,标记阶段标记出所有存活的对象。然后,将这些存活的对象向一端移动,清理掉不再使用的对象,并且更新指针,使得堆空间连续。
分代收集算法(Generational):将堆内存空间划分为不同的代,一般是年轻代(Young Generation)和老年代(Old Generation)。年轻代存放新创建的对象,老年代存放经过多次垃圾回收仍然存活的对象。不同代使用不同的垃圾回收算法,如年轻代使用复制算法,老年代使用标记-清除或者标记-整理算法。这种算法充分利用了不同对象的存活特性,提高了回收效率。
并发垃圾回收算法(Concurrent):在业务运行的同时进行垃圾回收。常见的并发垃圾回收算法有并发标记清除(CMS)和 G1(Garbage-First)算法。这些算法通常会在业务线程和垃圾回收线程之间进行协调,以最小化对业务线程的影响。
使用一些内存监控工具,日志,分析转储的日志文件,使用jps ,jstack等等
性能优化思路:先确定是哪方面的性能瓶颈,有很多包括硬件指标,操作系统,文件系统,io,中间件和数据库以及应用程序等等。我们说的性能优化一般都是只对于我们编写的应用程序的性能优化。 首先要通过测试衡量当前系统性能比如运行速度,相应时间,请求次数等等。再确定我们需要调优的目标,再找到性能的瓶颈代码,经过相关的优化策略进行优化。
出现慢查询的主要原因是一次性加载了大量的数据以及mysql同时需要分析大量数据行,可以在sql层面和在缓存等层面进行优化。在sql层面需要打开慢查询日志进行分析,找到执行较慢的sql语句,用explain进行分析然后针对性的优化如使用高效的索引和一次性查询过多无用数据等 。 在缓存层面可以 1 前端优化,处理查询次数,合并请求,会话保存等减少查询次数。2使用多级缓存去处理热点数据 3 服务拆分 4 读写分离,分库分表等等
索引的优化主要是通过分析explain执行计划来进行优化,尽量走覆盖索引和全值匹配,复合索引中要满足最左前缀原则等等
对于表的优化主要在与表结构和表大小的优化,尽量避免一个表的过多字段,使用合适的字段类型和大小。过大的表进行拆分等等 。
Mysql是一种关系型数据库,使用表格来存储数据,使用sql查询语言,支持水平拓展,支持acid事务,需要固定表字段,有非常活跃和完善的社区。
mongodb是一种nosql非关系型数据库,使用了类似于json的bosn语言对数据库进行操作,使用文档来存储数据,可以灵活的处理文档的数据和字段,同时提供了海量数据的存储,单个文档最多可存储16m数据,还提供了地理位置的数据结构,支持水平和垂直拓展,但是在单文档下不支持事务,多文档下的事务正在试用中。支持复杂的聚合查询。
一致性hash算法的出现目的是解决普通hash算法使用取模运算使得服务器资源变化导致的所有数据重新计算分布和缓存雪崩的问题。一致性hash算法使用了虚拟的hash环让服务器地址或主机名与2^32取模得到该服务器在这个hash环上的位置。当需要存储数据时通过哈希函数计算得到一个哈希值,并在哈希环上找到离该哈希值最近的节点,将数据存储在该节点上。一致性hash对于服务器的增减都只需要处理该服务器的部分数据,具有更好的容错性同时使用虚拟节点解决hash环的倾斜问题。
ArrayList和HashSet是Java中的两种不同的数据结构,它们有以下区别:
两者都是在多线程情况下用于并发控制,都是java中的关键字。有以下区别
实现方式:volatile是轻量级同步机制,不会进行加锁,没有加锁操作,每次获取共享变量都冲内存中获取而不会从缓存中获取,保证每次获取的都是最新的和保证可见性。synchronized底层通过互斥锁和监视器锁来实现,会有锁升级的过程。
作用范围:volatile作用于共享变量,synchronized可以作用于变量,代码块和方法
内存语义:volatile保证可见性,禁止重排序,不保证原子性 synchronized保证可见性,原子性和有序性
线程阻塞:volatile不会引起阻塞(没有加锁操作),synchronized会引起阻塞
多态原理基于继承,方法重写和父类引用指向子类对象
实现机制
索引结构:Innodb是聚簇索引,索引文件和数据记录在一起,找到索引就找到了记录。Myisam基于非聚簇索引,索引文件和记录文件不在一起,需要进行回表操作,先找到了主键再根据地址偏移量去查找。
事务支持: Innodb支持完整的ACID事务,Myisam不支持事务 因为Myisam不支持行锁只支持表锁,无法控制
数据一致性:Innodb支持外键约束,MyIsam不支持外键
基础数据结构 sds,inset,ziplist,dict ,quicklist,skiplist,redisObject
主要数据结构 String , hash , List , Set , Zset
是
在3.2版本之后,Redis统一采用QuickList来实现List,Redis的有序集合(Sorted Set,简称Zset)的底层实现使用了跳跃表(Skip List)和哈希表(Hash Table)两种数据结构的组合。
HTTP(Hypertext Transfer Protocol)和HTTPS(Hypertext Transfer Protocol Secure)是两种用于在客户端和服务器之间传输数据的协议,它们之间的主要区别体现在以下几个方面:
单例模式
在Java中,可以使用以下几种方式来实现线程间的同步:
悲观锁和乐观锁是在数据库中用来处理并发访问的两种不同的策略,它们在不同的应用场景下有所区别。
悲观锁的应用场景: