- 哪些对象可以作为GC Roots?
(1)虚拟机栈(栈帧中的本地变量表)中引用的对象。(2)方法区中类静态属性引用的对象。(3)方法区中常量引用的对象。(4)本地方法栈中JNI(即一般说的Native方法)引用的对象。
- ConcurrentHashMap的数据结构
在JDK1.7版本中,ConcurrentHashMap维护了一个Segment数组,Segment这个类继承了重入锁ReentrantLock,并且该类里面维护了一个 HashEntry<K,V>[] table数组,在写操作put,remove,扩容的时候,会对Segment加锁,所以仅仅影响这个Segment,不同的Segment还是可以并发的,所以解决了线程的安全问题,同时又采用了分段锁也提升了并发的效率。在JDK1.8版本中,ConcurrentHashMap摒弃了Segment的概念,而是直接用Node数组 链表 红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。
- tcp和udp的优点与缺点
(1)TCP的优点:可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。(2)TCP的缺点:慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。(3)UDP的优点:快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击…… (4)UDP的缺点:不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。基于上面的优缺点,那么:什么时候应该使用TCP:当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。在日常生活中,常见使用TCP协议的应用如下:浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输。什么时候应该使用UDP:当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。比如,日常生活中,常见使用UDP协议的应用如下:QQ语音 QQ视频 TFTP。
- HashMap原理
(1)hashMap 是非线程安全的, hashMap 1.7的底层实现为数组(table[])+链表(LinkList-->Entry),hashmap 1.8底层为数组 链表/红黑树(当链表长度到达阈值TREEIFY_THRESHOLD(默认为8)时,会转化为红黑树)
- HashMap的put和resize的过程
(1)put过程:①查看数组是否需要初始化 ②根据key计算hashcode ③根据hashcode计算出桶位置 ④遍历链表,查看key值与链表节点的key值是否相等,如果相等的话,那么进行覆盖旧值,并返回旧值。1.8的话需要先查看链表长度是否达到阈值,如果达到阈值,先进行红黑树转化然后再进行检查扩容。⑤新增的时候需要检查是否需要扩容,需要扩容的话进行两倍扩容,扩容完成后进行插入新值。(2)resize过程:resize扩容需要从四个方面来进行回答:①什么时候触发resize? 当容量超过当前容量(默认容量16)乘以负载因子(默认0.75)就会进行扩容,扩容大小为当前大小的两倍(扩展问题,为啥是两倍:通过限制length是一个2的幂数,h & (length-1)和h % length结果是一致的)。②resize是如何hash的:h & (length-1) ③resize是如何进行链表操作的:使用头插法进行数据插入,每次新put的值放在头部 ④并发操作下,链表是如何成环的:HashMap的环:若当前线程此时获得ertry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,因为并发transfer,所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候,由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行的时候,会将next节点,设置为自己,导致自己互相使用next引用对方,因此产生链表,导致死循环。
- 线程池有哪些类型
①FixedThreadPool:创建可重用固定线程数的线程池。②SingleThreadPool:创建只有一个线程的线程池。③CachedThreadPool:一个可根据需要创建新线程的线程池,如果现有线程没有可用的,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。④ScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
- ConcurrentHashMap分段锁原理
(1)ConcurrentHashMap采用了分段锁技术,其中Segement继承了RecentLock,当ConcurrentHashMap进行get、put操作时,均是同步的。各个Segement之间的get、put操作可以进行并发,即当一个线程访问ConcurrentHashMap的Segement时,不会影响对其他Segement的访问。
- B-树和B 树区别
1)B-树和B树是一个概念,是多路搜索树(相比于二叉搜索树,IO次数更少)。B-树的特性:①关键字集合分布在整颗树中;②任何一个关键字出现且只出现在一个结点中;③搜索有可能在非叶子结点结束;④其搜索性能等价于在关键字全集内做一次二分查找;⑤其最底搜索性能为O(logN) (2)B 树是B-树的变体,也是一种多路搜索树 B 的特性:①所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;②不可能在非叶子结点命中;③非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;④更适合文件索引系统;(3)B 树的优势:①单一节点存储更多的元素,使得查询的IO次数更少。②所有查询都要查找到叶子节点,查询性能稳定。③所有叶子节点形成有序链表,便于范围查询。
- Mysql数据库索引原理
(1)MyISAM索引实现:MyISAM引擎使用B Tree作为索引结构,叶节点的data域存放的是数据记录的地址。(2)Innodb索引实现:①第一个重大区别是InnoDB的数据文件本身就是索引文件。MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。②第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。
- 组合索引怎么使用?最左匹配的原理。
1)组合索引怎么使用?例如组合索引(a,b,c),组合索引的生效原则是:从前往后依次使用生效,如果中间某个索引没有使用,那么断点前面(范围值也算断点,orderby不算断点,用到索引)的索引部分起作用,断点后面的索引没有起作用;(2)最左匹配的原理:以最左边的为起点任何连续的索引都能匹配上
- Spring生命周期
Bean 的生命周期概括起来就是 4 个阶段:(1)实例化(Instantiation) (2)属性赋值(Populate) (3)初始化(Initialization) (4)销毁(Destruction)
- Spring几种scope区别?
(1)singleton:Spring的IOC容器中只有一个实例bean,该值为scope的默认值 (2)prototype:每次getBean时都会创建一个新的实例 (3)request:每次请求都会创建一个实体bean (4)session:每次session请求时都会创建一个实体bean (5)globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。
- Spring AOP实现有哪几种实现
接口代理和类代理会有什么区别?(1)Spring AOP有两种实现,均为动态代理:①JDK动态代理:基于反射进行动态代理,核心类是InvocationHandker类和Proxy类,被代理的类必须实现接口 ②CGLIB动态代理:被代理类无需实现接口,主要实现MethodInterceptor接口即可实现代理 (2)Spring AOP如果代理的类存在接口,优先使用JDK动态代理,否则使用CGLIB动态代理。
- MVCC,binlog,redolog,undolog都是什么,起什么作用?
(1)undolog 也就是我们常说的回滚日志文件 主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由引擎层的InnoDB引擎实现,是逻辑日志,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' ,那么undo日志就会用来存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事物用到该版本的信息时才可以清理相应undolog。它保存了事务发生之前的数据的一个版本,用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。(2)redoLog 是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。(3)MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。(4)binlog由Mysql的Server层实现,是逻辑日志,记录的是sql语句的原始逻辑,比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。产生:事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复
- Kafka是如何实现高吞吐率的?
1)顺序读写:kafka的消息是不断追加到文件中的,这个特性使kafka可以(2)充分利用磁盘的顺序读写性能 (3)零拷贝:跳过“用户缓冲区”的拷贝,建立一个磁盘空间和内存的直接映射,数据不再复制到“用户态缓冲区” (4)文件分段:kafka的队列topic被分为了多个区partition,每个partition又分为多个段segment,所以一个队列中的消息实际上是保存在N多个片段文件中 (5)批量发送:Kafka允许进行批量发送消息,先将消息缓存在内存中,然后一次请求批量发送出去 (6)数据压缩:Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩
- Http请求的完全过程
(1)浏览器根据域名解析IP地址(DNS),并查DNS缓存 (2)浏览器与WEB服务器建立一个TCP连接 (3)浏览器给WEB服务器发送一个HTTP请求(GET/POST):一个HTTP请求报文由请求行(request line)、请求头部(headers)、空行(blank line)和请求数据(request body)4个部分组成。(4)服务端响应HTTP响应报文,报文由状态行(status line)、相应头部(headers)、空行(blank line)和响应数据(response body)4个部分组成。(5)浏览器解析渲染
- Spring的@Transactional如何实现的?
(1)配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。(2)spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。(3)真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的
- 为什么要使用线程池?
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。(2)提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
- rpc框架实现原理?
主要有以下几个步骤:(1)建立通信:首先要解决通讯的问题:即A机器想要调用B机器,首先得建立起通信连接。主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有相关的数据都在这个连接里面进行传输交换。通常这个连接可以是按需连接(需要调用的时候就先建立连接,调用结束后就立马断掉),也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。(2)服务寻址:解决寻址的问题:即A机器上的应用A要调用B机器上的应用B,那么此时对于A来说如何告知底层的RPC框架所要调用的服务具体在哪里呢?通常情况下我们需要提供B机器(主机名或IP地址)以及特定的端口,然后指定调用的方法或者函数的名称以及入参出参等信息,这样才能完成服务的一个调用。比如基于Web服务协议栈的RPC,就需要提供一个endpoint URI,或者是从UDDI服务上进行查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。(3)网络传输:①序列化 当A机器上的应用发起一个RPC调用时,调用方法和其入参等信息需要通过底层的网络协议如TCP传输到B机器,由于网络协议是基于二进制的,所有我们传输的参数数据都需要先进行序列化(Serialize)或者编组(marshal)成二进制的形式才能在网络中进行传输。然后通过寻址操作和网络传输将序列化或者编组之后的二进制数据发送给B机器。②反序列化 当B机器接收到A机器的应用发来的请求之后,又需要对接收到的参数等信息进行反序列化操作(序列化的逆操作),即将二进制信息恢复为内存中的表达方式,然后再找到对应的方法(寻址的一部分)进行本地调用(一般是通过生成代理Proxy去调用, 通常会有JDK动态代理、CGLIB动态代理、Javassist生成字节码技术等),之后得到调用的返回值。(4)服务调用:B机器进行本地调用(通过代理Proxy)之后得到了返回值,此时还需要再把返回值发送回A机器,同样也需要经过序列化操作,然后再经过网络传输将二进制数据发送回A机器,而当A机器接收到这些返回值之后,则再次进行反序列化操作,恢复为内存中的表达方式,最后再交给A机器上的应用进行相关处理(一般是业务逻辑处理操作)。
- redis热key怎么解决?
(1)利用二级缓存:比如利用ehcache,或者一个HashMap都可以。在你发现热key以后,把热key加载到系统的JVM中。针对这种热key请求,会直接从jvm中取,而不会走到redis层。假设此时有十万个针对同一个key的请求过来,如果没有本地缓存,这十万个请求就直接怼到同一台redis上了。现在假设,你的应用层有50台机器,OK,你也有jvm缓存了。这十万个请求平均分散开来,每个机器有2000个请求,会从JVM中取到value值,然后返回数据。避免了十万个请求怼到同一台redis上的情形。(2)备份热key:这个方案也很简单。不要让key走到同一台redis上不就行了。我们把这个key,在多个redis上都存一份不就好了。接下来,有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据。
- 不同年代GC收集器有哪些?
(1)serial收集器:单线程,工作时必须暂停其他工作线程。多用于client机器上,使用复制算法 (2)ParNew收集器:serial收集器的多线程版本,server模式下虚拟机首选的新生代收集器。复制算法 (3)Parallel Scavenge收集器:复制算法,可控制吞吐量的收集器。吞吐量即有效运行时间。(4)Serial Old收集器:serial的老年代版本,使用整理算法。(5)Parallel Old收集器:第三种收集器的老年代版本,多线程,标记整理 (6)CMS收集器:目标是最短回收停顿时间。标记清除算法实现,分四个阶段:•初始标记:GC Roots直连的对象做标记 •并发标记:多线程方式GC Roots Tracing •重新标记:修正第二阶段标记的记录 •并发清除。缺点:标记清除算法的缺点,产生碎片。CPU资源敏感。
- ES脑裂问题分析及优化
(1)脑裂问题可能的成因 •网络问题:集群间的网络延迟导致一些节点访问不到master,认为master挂掉了从而选举出新的master,并对master上的分片和副本标红,分配新的主分片 •节点负载:主节点的角色既为master又为data,访问量较大时可能会导致ES停止响应造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。•内存回收:data节点上的ES进程占用的内存较大,引发JVM的大规模内存回收,造成ES进程失去响应。(2)脑裂问题解决方案:•减少误判:discovery.zen.ping_timeout节点状态的响应时间,默认为3s,可以适当调大,如果master在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如6s,discovery.zen.ping_timeout:6),可适当减少误判。•选举触发 discovery.zen.minimum_master_nodes:1 该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值,且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(n/2) 1,n为主节点个数(即有资格成为主节点的节点个数) 增大该参数,当该值为2时,我们可以设置master的数量为3,这样,挂掉一台,其他两台都认为主节点挂掉了,才进行主节点选举。•角色分离:即master节点与data节点分离,限制角色
- 讲一讲类加载的过程
一般来说,我们把 Java 的类加载过程分为三个主要步骤:加载,连接,初始化,具体行为在 Java 虚拟机规范里有非常详细的定义。(1)首先是加载过程(Loading),它是 Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象),这里的数据源可能是各种各样的形态,比如 jar 文件,class 文件,甚至是网络数据源等;如果输入数据不是 ClassFile 的结构,则会抛出 ClassFormatError。加载阶段是用户参与的阶段,我们可以自定义类加载器,去实现自己的类加载过程。(2)第二阶段是连接(Linking),这是核心的步骤,简单说是把原始的类定义信息平滑地转入 JVM 运行的过程中。这里可进一步细分成三个步骤:1,验证(Verification),这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合 Java 虚拟机规范的,否则就被认为是 VerifyError,这样就防止了恶意信息或者不合规信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。2,准备(Pereparation),创建类或者接口中的静态变量,并初始化静态变量的初始值。但这里的“初始化”和下面的显示初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会去执行更进一步的 JVM 指令。3,解析(Resolution),在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。在 Java 虚拟机规范中,详细介绍了类,接口,方法和字段等各方面的解析。(3)最后是初始化阶段(initialization),这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。再来谈谈双亲委派模型,简单说就是当加载器(Class-Loader)试图加载某个类型的时候,除非父类加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载 Java 类型。
- 不可重复读和幻读区别
(1)"不可重复读" 是指在一个事务内,多次读同一数据。在这个事务还没有结束时,bai另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。(2)幻觉读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
- 单例对象会被jvm的gc时回收吗
(1)jvm卸载类的判定条件如下:①该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。②加载该类的ClassLoader已经被回收。③该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。(2)只有上面三个条件都满足,jvm才会在垃圾收集的时候卸载类。显然,单例的类不满足条件一,因此单例类也不会被卸载。也就是说,只要单例类中的静态引用指向jvm堆中的单例对象,那么单例类和单例对象都不会被垃圾收集,依据根搜索算法,对象是否会被垃圾收集与未被使用时间长短无关,仅仅在于这个对象是不是不可回收的。
- Get和Post区别
(1)Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。(2)Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制。(3)Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。(4)Get执行效率却比Post方法好。Get是form提交的默认方法。GET产生一个TCP数据包;POST产生两个TCP数据包。(非必然,客户端可灵活决定)
- 死锁的4个必要条件
(1)互斥条件:一个资源每次只能被一个线程使用;(2)请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;(3)不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺;(4)循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
- Redis 的数据结构及使用场景
1)String字符串:字符串类型是 Redis 最基础的数据结构,首先键都是字符串类型,而且 其他几种数据结构都是在字符串类型基础上构建的,我们常使用的 set key value 命令就是字符串。常用在缓存、计数、共享Session、限速等。(2)Hash哈希:在Redis中,哈希类型是指键值本身又是一个键值对 结构,添加命令:hset key field value。哈希可以用来存放用户信息,比如实现购物车。(3)List列表(双向链表):列表(list)类型是用来存储多个有序的字符串。可以做简单的消息队列的功能。(4)Set集合:集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一 样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过 索引下标获取元素。利用 Set 的交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。(5)Sorted Set有序集合(跳表实现):Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。可以做排行榜应用,取 TOP N 操作
- ZAB协议
ZAB协议包括两种基本的模式:崩溃恢复和消息广播。当整个 Zookeeper 集群刚刚启动或者Leader服务器宕机、重启或者网络故障导致不存在过半的服务器与 Leader 服务器保持正常通信时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。当集群中超过半数机器与该 Leader 服务器完成数据同步之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。
- volatile作用
(1)volatile在多处理器开发中保证了共享变量的“ 可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。(共享内存,私有内存) (2)volatile关键字通过“内存屏障”来防止指令被重排序。
- 什么是值传递和引用传递
(1)值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量. (2)引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。
- Java内存模型
Java虚拟机规范中将Java运行时数据分为六种: (1)程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。(2)Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。(3)本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。(4)Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。(5)方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。(6)运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。
- string.stringbuilder.stringbuffer的区别,为什么string不可变
(1)区别 ①String是字符串常量,而StringBuffer和StringBuilder是字符串变量。由String创建的字符内容是不可改变的,而由StringBuffer和StringBuidler创建的字符内容是可以改变的。②StringBuffer是线程安全的,而StringBuilder是非线程安全的。StringBuilder是从JDK 5开始,为StringBuffer类补充的一个单线程的等价类。我们在使用时应优先考虑使用StringBuilder,因为它支持StringBuffer的所有操作,但是因为它不执行同步,不会有线程安全带来额外的系统消耗,所以速度更快。(2)String为什么不可变:虽然String、StringBuffer和StringBuilder都是final类,它们生成的对象都是不可变的,而且它们内部也都是靠char数组实现的,但是不同之处在于,String类中定义的char数组是final的,而StringBuffer和StringBuilder都是继承自AbstractStringBuilder类,它们的内部实现都是靠这个父类完成的,而这个父类中定义的char数组只是一个普通是私有变量,可以用append追加。因为AbstractStringBuilder实现了Appendable接口
- 为什么在重写equals方法的时候要重写hashcode的方法?
(1)我们知道判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,造成hashcode的值不同,而equals()方法判断出来的结果为true。(2)在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals()的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。
- 反射的作用是什么?
(1)反射的主要作用是用来扩展系统和动态调用程序集。(2)所谓扩展系统就是先把系统写好,系统里面定义接口,后面开发的人去写接口的代码。(3)动态调用程序集就是利用反射去调用编译好的dll,当然此时的dll没有被引用到你所建的工程里面。
- 同步与异步区别?
(1)同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。(2)异步,执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。
- Java中overload override的区别
(1)Overload是重载的意思,Override是覆盖的意思,也就是重写。(2)重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中定义的方法,这相当于把父类中的方法给覆盖了,这也是多态性的一种表现。(3)重载overload的特点就是与返回值无关,只看参数列表,所以重载的方法可以改变返回值类型。所以,如果两个方法的参数列表完全一样,是不能通过让它们的返回值类型不同来实现重载的。(4)override是覆盖一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。(5)overload对我们来说可能比较熟悉,可以翻译为重载,它是指我们可以定义一些名称相同的方法,通过定义不同类型的输入参数来区分这些方法,然后再调用时,JVM就会根据不同的参数样式,来选择合适的方法执行。(6)方法的重写和重载是Java多态性的不同表现。重写是父类与子类之间多态性的一种表现,而重载是一个类中多态性的一种表现。
- 线程的创建方式
(1)继承Thread ①定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。②创建Thread子类的实例,即创建了线程对象。③调用线程对象的start()方法来启动该线程。(2)实现Runnable接口 ①定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。②创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。③调用线程对象的start()方法来启动该线程。(3)实现Callable接口 ①创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。②创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。③使用FutureTask对象作为Thread对象的target创建并启动新线程。④调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
- LinkList和ArrayList的区别
(1)ArrayList的底层实现就是数组,且ArrayList实现了RandomAccess,表示它能快速随机访问存储的元素,通过下标 index 访问,只是我们需要用 get() 方法的形式,数组支持随机访问,查询速度快,增删元素慢;(2)LinkedList的底层实现是链表,LinkedList没有实现RandomAccess 接口,链表支持顺序访问,查询速度慢,增删元素快;
- 垃圾回收算法
(1)标记—清除算法 标记—清除算法是最基础的收集算法,它分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的可达性分析算法中判定垃圾对象的标记过程。(2)复制算法 复制算法是针对标记—清除算法的缺点,在其基础上进行改进而得到的,它将可用内存按容量分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面,然后再把已使用过的内存空间一次清理掉。(3)标记—整理算法 复制算法比较适合于新生代,在老年代中,对象存活率比较高,如果执行较多的复制操作,效率将会变低,所以老年代一般会选用其他算法,如标记—整理算法。该算法标记的过程与标记—清除算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存
- Hashtable 和 HashMap的区别?
( 1)主要区别在于 HashMap允许将 null作为一个 entry的 key或者 value,而 Hashtable不允许。由于非线程安全,多线程情况下,效率上可能高于 Hashtable。Hashtable和 HashMap采用的 hash/rehash 算法都大概一样,所以单线程性能不会有很大的差异。( 2) Hashtable的方法是 Synchronize的,而 HashMap不是,在多个线程访问 Hashtable时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供外同步 (Collections.synchronizedMap)。( 3) HashMap是 Hashtable的轻量级实现(非线程安全的实现),他们都完成了 Map接口, Hashtable继承自 Dictionary类,而 HashMap是 Java1.2引进的 Map interface的一个实现。
- servlet的线程安全问题?
答:如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。那么我们都知道servlet是多线程的,同时一个servlet实现类只会有一个实例对象,也就是它是Singleton的,所以多个线程是可能会访问同一个servlet实例对象的。同一个实例对象被多个线程访问,如果没有做同步处理,那么servlet就是非线程安全的,如果做了同步处理,就是线程安全。所以,servlet是否线程安全是由它的实现来决定的,如果它内部的属性或方***被多个线程改变,它就是线程不安全的,反之,就是线程安全的。