首页 > 技术岗面试高频考点(含答案)合集,精心准备万字考点
头像
快人一步拿offer
编辑于 2021-04-18 12:00
+ 关注

技术岗面试高频考点(含答案)合集,精心准备万字考点

以下内容都是面试时的高频考点,分为操作系统、计算机网络、数据结构、数据库、算法几个方面,都是自己一点一点整理的,本人最后也拿到了美团、拼多多、京东、网易互娱等多家offer,分享给大家~

操作系统

1. 死锁

死锁的必要条件:

  • 1)     互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 2)     请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 3)     不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 4)     环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

死锁预防:

  • 1)     资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  • 2)     只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  • 3)     可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
  • 4)     资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

避免死锁:银行家算法

2. 线程安全

定义:当多个线程同时访问一个对象时,如果不用考虑线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,调用这个对象都能获得正确的结果,那么就称这个对象是线程安全的。

3. BIO/NIO/AIO

Select:当有一个或多个流的I/O事件就绪时,就从阻塞状态中醒来,然后轮询一遍所有的流,处理已经准备好的I/O事件。轮询的过程可以参照如下:select那里只是知道了有I/O事件准备好了,但不知道具体是哪几个流(可能有一个,也可能有多个),所以需要无差别的轮询所有的流,找出已经准备就绪的流。可以看到,使用select时,我们需要O(n)的时间复杂度来处理流,处理的流越多,消耗的时间也就越多。

4. 虚拟内存

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

虚拟地址空间按照固定大小划分成被称为页面(page)的若干单元。在物理内存中对应的单元称为页框。页面和页框的大小通常是一样的。

虚拟地址被分成虚拟页号(高地址)和偏移量(低地址)两部分。不同的划分对应了不同的页面大小。

5. 进程间通信方式

  • 1)管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程之间使用。进程的亲缘关系通常是指父子进程关系。
  • 2)有名管道(FIFO文件,借助文件系统):有名管道也是半双工的通信方式,但是允许在没有亲缘关系的进程之间使用,管道是先进先出的通信方式。
  • 3)共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与信号量,配合使用来实现进程间的同步和通信。
  • 4)消息队列:消息队列是有消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 5)信号:用于通知接收进程某个事件已经发生,比如按下ctrl + C就是信号。
  • 5)信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,实现进程、线程的对临界区的同步及互斥访问。
  • 6)套接字:适用于不同机器间进程通信,在本地也可作为两个进程通信的方式。


计算机网络

6. TCP三次握手和四次挥手

7. 流量控制、拥塞控制、快重传和快回复

流量控制:滑动窗口协议

拥塞控制:“慢启动”(Slow Start)、“拥塞避免”(Congestion voidance)、“快速重传”(Fast Retransmit)、“快速恢复”(Fast Recovery)。

发送方维持一个叫做拥塞窗口cwndcongestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。

  • cwnd<ssthresh时,使用慢开始算法。
  • cwnd>ssthresh时,改用拥塞避免算法。
  • cwnd=ssthresh时,慢开始与拥塞避免算法任意

快重传和快恢复:快速重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有达到对方)而不要等到自己发送数据时才进行捎带确认。

快重传算法规定,发送方只要一连收到3个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计数器时间到期。

8. https加密过程

怎么验证证书的有效性
  • 证书是否是信任的有效证书。所谓信任:浏览器内置了信任的根证书,就是看看web服务器的证书是不是这些信任根发的或者信任根的二级证书机构颁发的。所谓有效,就是看看web服务器证书是否在有效期,是否被吊销了。
  • 对方是不是上述证书的合法持有者。简单来说证明对方是否持有证书的对应私钥。验证方法两种,一种是对方签个名,我用证书验证签名;另外一种是用证书做个信封,看对方是否能解开。以上的所有验证,除了验证证书是否吊销需要和CA关联,其他都可以自己完成。验证正式是否吊销可以采用黑名单方式或者OCSP方式。黑名单就是定期从CA下载一个名单列表,里面有吊销的证书序列号,自己在本地比对一下就行。优点是效率高。缺点是不实时。OCSP是实时连接CA去验证,优点是实时,缺点是效率不高。

9. HTTP/TCP/UDP/IP格式

这个答案自己查一下,因为图放上来会看不清楚,就不放了

10. http状态码

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误

11. http的几个版本差别

http1.1

增加了状态码
Host头处理
支持长连接和pipeline,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。
HTTP1.1增加了OPTIONS, PUT, DELETE, TRACE, CONNECT这些Request方法.

HTTP2.0和HTTP1.X相比的新特性

新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。

数据结构

12. 排序算法


快排的空间复杂度是O(logn)

13. 一致性哈希

数据库

14. 锁的分类

共享锁:共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一锁,都能访问到数据,但是只能读不能修改。
排他锁:排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
表级锁: 每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
行级锁: 每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
页级锁

15. 数据库隔离级别

脏读是指一个事务读取了未提交事务执行过程中的数据。

不可重复读:事务的过程中,一行数据被检索两次且行内的值读出之间不同发生。

幻读:A phantom read occurs when, in the course of a transaction, new rows are added or removed by another transaction to the records being read. 当一个事务同时进行两次select操作,另一个事务在期间进行了增加或删除操作会导致幻读。

16. 事务的ACID

原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency):事务前后数据的完整性必须保持一致。
隔离性(Isolation):事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

17.索引类型

主键索引,普通索引,唯一索引,全文索引,组合索引

18. 数据库索引为什么使用B+树

B+树能够减少IO次数。B+树的结点包含多个关键字,很多数据库索引使用节点大小恰好等于操作系统一页大小的B+树来实现是效率最高的选择。红黑数的一个节点无法填满一页,适合内存结构,不适合磁盘存储中。

19. Mysam和InnoDb的区别

1)     InnoDB支持事务,MyISAM不支持
2)     InnoDB支持外键,而MyISAM不支持
3)     InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁
4)     Innodb存储文件有frm、ibd,而Myisam是frm、MYD、MYI
5)     InnoDB表必须有主键(用户没有指定的话会自己找或生产一个主键),而Myisam可以没有
6)     Mysam会一次性获得所有的锁,没有死锁问题。InnoDB发生死锁后一般能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。可以通过对表的排序来顺便访问表,可以大大减少死锁的概率。

20. 分库分表

包括水平拆分和垂直拆分
垂直分表:
也就是“大表拆小表”,基于列字段进行的。一般是表中的字段较多,将不常用的, 数据较大,长度较长(比如text类型字段)的拆分到“扩展表“。一般是针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。
垂直分库针对的是一个系统中的不同业务进行拆分,比如用户User一个库,商品Producet一个库,订单Order一个库。 切分后,要放在多个服务器上,而不是一个服务器上。为什么? 我们想象一下,一个购物网站对外提供服务,会有用户,商品,订单等的CRUD。没拆分之前, 全部都是落到单一的库上的,这会让数据库的单库处理能力成为瓶颈。按垂直分库后,如果还是放在一个数据库服务器上,随着用户量增大,这会让单个数据库的处理能力成为瓶颈,还有单个服务器的磁盘空间,内存,tps等非常吃紧。 所以我们要拆分到多个服务器上,这样上面的问题都解决了,以后也不会面对单机资源问题。
水平拆分:分库分表出现的问题
分布式事务
跨库join
跨节点的count,order by,group by以及聚合函数问题 这些是一类问题
数据迁移,容量规划,扩容等问题
ID问题 一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制

Spring/Spring Boot

21. IOC

22. AOP

  • AOP是面向切片编程,能够讲与业务无关,却为业务模块所共同调用的逻辑封装起来,例如事务,日志,减少重复代码,降低耦合度,有利于未来的可扩展性和可维护性。
  • 实现方式:Spring AOP的动态代理主要有两种方式实现,JDK动态代理和cglib动态代理。JDK动态代理通过反射来接收被代理的类,但是被代理的类必须实现接口,核心是InvocationHandler和Proxy类。cglib动态代理的类一般是没有实现接口的类,cglib是一个代码生成的类库,可以在运行时动态生成某个类的子类,所以,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
  • JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。
  • cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

23.  Spring事务传播行为


JAVA基础

24. JAVA基本数据类型

Byte,short,int,long
Float,double
Char
Boolean

25. 权限修饰符


26. 多态性

当运行时调用引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就表现出:相同类型的变量调用同一个方法时表现出不同的行为特征,这就是多态。

27. JAVA对象创建过程

1)     如果该类没有加载,需要先进行类加载过程
2)     CAS在堆分配内存空间
3)     一个对象包括四部分,对象头(MARKWORD),类对象的引用,实例数据,对齐填充
4)     执行初始化方法
5)     栈上的引用指向堆对象

28. 划分内存的方法

“指针碰撞”(Bump the Pointer)
如果Java堆中内存是绝对规整的,所有用过的内 存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。
“空闲列表”(Free List)
如果Java堆中的内存并不是规整的,已使用的内存和空 闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例, 并更新列表上的记录

29.  List, Set, Map

List: ArrayList, LinkedList, Vector(线程安全)
Set: HashSet, LinkedHashSet, Treeset
Map: HashMap, TreeMap, HashTable(线程安全

30. 怎么保证List的线程安全

1)     使用Vector
2)     使用Collections.synchronizedList(new ArrayList<>())
3)     CopyOnWriteArrayList(利用synchronized 进行写时复制)

31. HashMap和concurrenthashmap

HashMap
增加超过阈值将会从链表改变成红黑树
容量超过75%的时候,容量扩充一倍
HashMap不是线程安全的,不能再读的时候同时进行写的操作

Concurrenthashmap:
JDK1.5采用分段锁
JDK1.8采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。
synchronized只锁定当前链表或红黑二叉树的首节点。对于put操作,如果Key对应的数组元素为null,则通过CAS操作将其设置为当前值。如果Key对应的数组元素(也即链表表头或者树的根元素)不为null,则对该元素使用synchronized关键字申请锁,然后进行操作。如果该put操作使得当前链表长度超过一定阈值,则将该链表转换为树,从而提高寻址效率。

32. ThreadLocal

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value); }

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

33. 抽象类和接口的区别

1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
3)接口强调特定功能的实现,而抽象类强调所属关系。
4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。

34. 匿名内部类和lamda表达式的区别

第一点:所需类型不同
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类。
Lambda表达式:只能是接口。
第二点:使用限制不同
1)如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类。
2)如果接口中有多于一个抽象方法,只能使用匿名内部类,不可以使用Lambda表达式。
第三点:实现原理不同
匿名内部类:编译之后会产生一个单独的.class字节码文件。
Lambda表达式:编译之后没有产生一个单独的.class字节码文件,对应的字节码文件会在运行的时候动态生成。

35. JVM垃圾回收

垃圾回收的时间:
1)     CPU空闲的时候
2)     堆满的时候
3)     调用System.gc()
如何判断对手能够被回收:
1)     引用计数法(引用数为0),缺点是它很难解决对象之间相互循环引用的问题。
2)     可达性分析算法(根搜索算法)

36. 如何清除对象

1)     标记清理算法,标记清除之后会产生大量不连续的内存碎片,当程序在运行过程中需要分配较大对象时,无法找到足够的连续内存而造成内存空间浪费
2)     标记-整理算法
3)     复制算法,复制算法将可用内存划分成大小相等的两块A和B,每次只使用其中一块,当A的内存用完了,就把存活的对象复制到B,并清空A的内存,不仅提高了标记的效率,因为只需要标记存活的对象,同时也避免了内存碎片的问题,代价是可用内存缩小为原来的一半。
4)分代收集算法:分代收集算法是一种比较智能的算法,也是现在jvm使用最多的一种算法,它本身其实不是一个新的算法,而是他会在具体的场景自动选择以上三种算法进行垃圾对象回收。在jdk1.7之前,对JVM分为三个区域:新生代、老年代、永久代
**一般年轻代用标记复制算法,老年代用标记整理算法

37. 垃圾回收器

Java8:Parallel Scanvenge+Parallel Old ,Scavenge目标是达到可控制的吞吐量
CMS:以最短回收停顿为目标,基于标记-清除
四个步骤:初始标记(STW),并发标记,重新标记(STW)、并发清除
缺点:(1)并发阶段占用处理及资源 (2)CMS运行过程中用户线程继续执行,不能老年代满后触发FullGC,需要预留一部分空间 (3) 产生碎片,无法分配大对象时需要开启合并整理的过程。

38. G1:从总体来看是标记-整理,局部(两个Region)基于标记-复制

把连续的堆分成多个独立的区域(Region),将Region作为单次回收的最小单位,G1收集器跟踪各个Reigon里面垃圾堆积的“价值”大小,优先回收价值大的的Region

39. JVM内存模型

  • Java虚拟机的内存空间分为五个部分,分别是:
  • 程序计数器
  • Java虚拟机栈:线程私有的,生命周期和线程一样。虚拟机栈描述的是方法执行时的内存模型,每个方法执行的时候同时会创建一个栈帧,用来存放局部变量表,操作数栈,动态链接,方法出口等信息。
  • 本地方法栈:Native Method 原生方法是Java调用非Java方法接口
  • 堆:可以被分为新生代和老年代。几乎所有的对象都存储在堆中,所有线程访问同一个堆,常量池存放再堆中
  • 方法区。方法区中存放已经被虚拟机加载的类信息、常量、静态变量
一个线程抛出OOM后,会释放它所占据的内存空间,别的线程会继续执行
JVM内存分析工具:jps, jconsole, jmap, visualVM

40.  类加载

类加载的过程
1)     通过Class的全限定名获取Class的二进制字节流
2)     将Class的二进制内容加载到虚拟机的方法区
3)     在内存中生成一个java.lang.Class对象表示这个Class

41. 双亲委托模型


工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,只有当父类加载器无法加载时,子加载器才会尝试自己去完成加载。
Bootstrap Class Loader加载JAVA_HOME\lib下的类
Extension加载JAVA_HOME\lib\ext
Application加载用户类路径(ClassPath)上的所有类库

42. 类加载的时机

1)     遇到new、getstatic 和 putstatic 或 invokestatic 这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。对应场景是:使用 new 实例化对象、读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法。
2)     对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3)     当初始化类的父类还没有进行过初始化,则需要先触发其父类的初始化。(而一个接口在初始化时,并不要求其父接口全部都完成了初始化)
4)     虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类。

43. Object

方法包括:getClass、equals、hashCode、notify、notifyAll、wait、clone、finalize

44.  如何调整JAVA运行内存

1)-Xms <size> 设置jvm可用堆内存的初始大小
2)-Xmx<size> 设置jvm堆内存的最大可用空间
3)-Xmn<size> 设置新生代的大小
4)XX:NewSize:设置新生代的大小
5)XX:NewRatio:设置老年代与新生代的比例,即老年代除以新生代大小

45. 线程安全的单例模式

//双重检查锁
public final class DoubleCheckedSingleton
{
private static violatile DoubleCheckedSingleton singleObj = null;
private DoubleCheckedSingleton(){  }
public static DoubleCheckedSingleton getSingleInstance(){
if(null == singleObj ) {
Synchronized(DoubleCheckedSingleton.class){
if(null == singleObj)
singleObj = new DoubleCheckedSingleton();
}
}
return singObj;
}
}

45. Java对象引用

  • 强引用
  • 弱引用:一旦进行垃圾回收就会进行回收
  • 软引用:对象将被尽可能长时间的保存,一直到出现内存不足的情况下才会被回收,可用于缓存之中
  • 虚引用(幽灵引用):这种操作返回的内容永远都是null,就是不引用,在对象垃圾收集前一定会调用 finalizes0方法,但是如果说 finalized又占着其它的强引用的对象不放,这个会造成逻辑上的死锁,所以这个时候可以通过虚引用来解决。

46. Nignx

负载均衡算法:
1)     轮询       默认方式
2)     weight   权重方式
3)     ip_hash 依据ip分配方式
4)     url_hash(第三方)    依据URL分配方式
5)     least_conn   最少连接方式
6)     fair(第三方)     响应时间方式

47. Synchronized

java加锁过程:偏向锁->轻量级锁->重量级锁
(1)初次执行到synchronized代码块的时候,锁对象变成偏向锁(通过CAS修改对象头里的锁标志位),字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后,线程并不会主动释放偏向锁。当第二次到达同步代码块时,线程会判断此时持有锁的线程是否就是自己(持有锁的线程ID也在对象头里),如果是则正常往下执行。由于之前没有释放锁,这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。偏向锁只需要一次CAS,轻量级锁每次都需要CAS操作。
(2)一旦有第二个线程加入锁竞争,偏向锁就升级为轻量级锁(自旋锁)。这里要明确一下什么是锁竞争:如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。
注意:第二个线程对偏向锁对象加锁,如果持有偏向锁的线程不存活,那么只需要修改偏向,不需要升级为轻量级锁。如果持有偏向锁的线程存活,那么根据锁对象是否被锁定来撤销偏向,撤销后锁对象为未锁定或者轻量级锁定状态。**
(3)在轻量级锁状态下继续锁竞争,没有抢到锁的线程将自旋,即不停地循环判断锁是否能够被成功获取。获取锁的操作,其实就是通过CAS修改对象头里的锁标志位。先**比较**当前锁标志位是否为“释放”,如果是则将其**设置**为“锁定”,比较并设置是**原子性**发生的。这就算抢到锁了,然后线程将当前锁的持有者信息修改为自己
(4)长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)。如果多个线程用一个锁,但是没有发生锁竞争,或者发生了很轻微的锁竞争,那么synchronized就用轻量级锁,允许短时间的忙等现象。这是一种折衷的想法,短时间的忙等,换取线程在用户态和内核态之间切换的开销。
显然,此忙等是有限度的(有个计数器记录自旋次数,默认允许循环10次,可以通过虚拟机参数更改)。如果锁竞争情况严重,某个达到最大自旋次数的线程,会将轻量级锁升级为重量级锁(依然是CAS修改锁标志位,但不修改持有锁的线程ID)。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。在JDK1.6之前,synchronized直接加重量级锁,很明显现在得到了很好的优化。

48. Synchronized与ReentrantLock区别

功能方面:对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized
ReentrantLock特点:
1)     等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待
2)     公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁
3)     锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

49. CAS

https://www.jianshu.com/p/ae25eb3cfb5d
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
CAS的缺点:
1)CPU开销较大:在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2)不能保证代码块的原子性:CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
3)ABA问题
Volatile:
在双重校验锁情况下,不加volatile可能拿到半初始化对象

50. wait和sleep的区别

同:
都是线程同步时会用到的方法,使当前线程暂停运行,把运行机会交给其它线程。
如果任何线程在等待期间被中断都会抛出InterruptedException
都是native方法
异:
所在类不同,wait()是Object超类中的方法;而sleep()是线程Thread类中的方法
关键点是对锁的保持不同,wait会释放锁;而sleep()并不释放锁
唤醒方法不完全相同,wait依靠notify或者notifyAll、中断发生、或者到达指定时间来唤醒;而sleep()则是到达指定的时间后被唤醒。
使用的位置不同,wait只能用在同步代码块中,而sleep用在任何位置。

51.  线程池

52. 反射

将类的各个组成部分封装成其他对象,这就是反射机制

反射的好处:解构,提高程序的可扩展性。

53. 获取Class类对象的方式:

1)     Class.forName() 将字节码文件加载进内存
2)     类名.class
3)     对象.getClass
三种方式获得的对象是“==”的,说明同一个字节码文件在程序运行中智慧被加载一次。

54. 解析注解

1)     获取注解定义位置的对象
2)     获取指定注解(在内存中生成了注解接口的实现对象)
3)     调用注解的抽象方法

55. 注解

注解的作用:
1)     编写文档
2)     编译检查
3)     代码分析
JDK预定义的注解:
@Override
@Deprecated
@SuppressWarnings
注解格式:
元注解
public @interface 注解名称(){}
注解本质:Public interface 注解名称 extends java.lang.annotation.Annotation,即注解本质上是一个接口,继承了Annotation接口

56. 并发

Synchronized
Synchronized关键字经过javac编译之后,会在同步块前后分别形成monitorenter和monitorexit这两个字节码指令,如果指定了锁对象,那么就以这个对象的引用作为reference;如果没有指定,那根据修饰的方法类型(对象方法或者类方法)来决定取对象实例还是类的Class对象作为线程持有的锁

Redis

57. Redis为什么是单线程的?

因为CPU不是Redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽。(以上主要来自官方FAQ)既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

58. Redis快速的原因

1)绝大部分请求是纯粹的内存操作(非常快速)
2)采用单线程,避免了不必要的上下文切换和竞争条件
3)使用多路 I/O 复用模型

59. Redis持久化机制

1)RDB
RDB是Redis默认持久化机制。RDB相当于快照,保存的是一种状态
优点:保存速度、还原速度极快;适用于灾难备份。
缺点:小内存的机器不符合使用。RDB机制符合要求就会快照。
2)AOF
如果Redis意外down掉,RDB方式会丢失最后一次快照后的所有修改。如果要求应用不能丢失任何修改,可以采用AOF持久化方式。
AOF:Append-Only File:Redis会将没一个收到写命令都追加到文件中(默认是appendonly.aof)。当Redis重启时会通过重新执行文件中的写命令重建整个数据库的内容。
产生的问题:有些命令是多余的。

60. Redis数据类型

string,list,set,zset,hashmap
数据存储的底层数据结构:
String:用sds实现,len表示sdshdr中数据的长度,free表示sdshdr中剩余的空间,buf表示实际存储数据的空间。
List:当列表对象同时满足以下两个条件时,列表对象使用ziplist进行存储,否则用linkedlist存储
(1)列表对象保存的所有字符串元素的长度小于64字节
(2)列表对象保存的元素数量小于512个。
zset底层的存储结构包括ziplist或skiplist,在同时满足以下两个条件的时候使用ziplist,其他时候使用skiplist,两个条件如下:(1)有序集合保存的元素数量小于128个(2)有序集合保存的所有元素的长度小于64字节

61. Redis和数据库的同步的解决方式

1)     一致性要求高场景,实时同步方案,即查询redis,若查询不到再从DB查询,保存到redis;
2)     并发程度高的,采用异步队列的方式,采用kafka等消息中间件处理消息生产和消费
3)     阿里的同步工具canal,实现方式是模拟mysql slave和master的同步机制,监控DB bitlog的日志更新来触发redis的更新,解放程序员双手,减少工作量
4)     利用mysql触发器的API进行编程,c/c++语言实现,学习成本高

62. 缓存穿透、缓存雪崩

缓存穿透:就是客户持续向服务器发起对不存在服务器中数据的请求。客户先在Redis中查询,查询不到后去数据库中查询。
缓存击穿:就是一个很热门的数据,突然失效,大量请求到服务器数据库中
缓存雪崩:就是大量数据同一时间失效。
缓存穿透:
1.接口层增加校验,对传参进行个校验,比如说我们的id是从1开始的,那么id<=0的直接拦截;
2.缓存中取不到的数据,在数据库中也没有取到,这时可以将key-value对写为key-null,这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿:
最好的办法就是设置热点数据永不过期,拿到刚才的比方里,那就是你买腾讯一个永久会员
缓存雪崩:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。

63. Redis主从复制、哨兵模式、集群模式

全部评论

(2) 回帖
加载中...
话题 回帖

推荐话题

相关热帖

热门推荐