面试常见基础知识点及问题

目录

  • 线程池
  • ConcurrentHashMap
  • volatile关键字
  • AQS
  • synchronized实现原理
  • CAS操作
  • MySQL索引类型和区别
  • 事务的四大特性
  • 事务的隔离级别
  • sql优化

1、线程池(Executors)

首先线程池是一个工厂方法,我们常见的四种线程池

  • Executors.newCacheThreadPool():可缓存线程池
  • Executors.newFixedThreadPool(int n):可重用固定个数的线程池
  • Executors.newScheduledThreadPool(int n):定长线程池,支持定时及周期性任务执行
  • Executors.newSingleThreadExecutor():单线程化的线程池

其实都是初始化了同一个构造方法,只是参数不同而已
在这里插入图片描述
阿里标准推荐自定义线程池,也就是根据自己的需要来合理的输入参数,创建自己理想的线程池
六个参数定义如下:

  • int corePoolSize:核心线程数
  • int maximumPoolSize:最大线程数
  • long keepAliveTime:存活时间(空闲)
  • TimeUnit unit:存活时间单位
  • BlockingQueue workQueue:阻塞队列
  • RejectedExecutionHandler handler:拒绝策略(任务满了)

流程:如果投入线程数小于核心线程数,那么线程池会创建对饮数目的线程,如果大于,会先存放到阻塞队列中,如果阻塞队列也满了,会继续创建线程,直到达到最大线程数,当线程数目达到最大线程数,并且阻塞队列也满了,便会去执行我们的拒绝策略。

jdk提供的四种缺省的拒绝策略

  • AbortPoliey:直接抛出异常(默认策略)
  • DiscardPolicy:之际丢弃
  • CallerRunsPolicy:调用当前提交任务的线程来执行
  • DiscardOldestPolicy:阻塞队列最靠前的任务丢弃

如果都不想用,便可以实现RejectedExecutionHandler接口自定义拒绝策略。

2、ConcurrentHashMap

线程安全的HashMap
从源码层次看一下put(key,value)方法:
在这里插入图片描述
put方法只是其线程安全的期中一个因素,其根本原因是因为ConcurrentHashMap底层结构是一个Segment数组

这就是为什么ConcurrentHashMap支持允许多个修改同时并发进行,原因就是采用的Segment分段锁功能,每一个Segment 都有自己锁,只要修改不再同一个段上就不会引起并发问题。

  • 需要注意的是 Segment 是一种可重入锁(继承ReentrantLock)

那ReentrantLock 与synchronized有什么区别?

  • synchronized 是一个同步锁,当当前资源同步块已经被线程访问时,其他线程便会受到阻塞,不可访问,只能等待,或访问其他资源同步块。等到该资源同步块被释放后才可访问,
    synchronized是一个非公平性锁。 非公平性锁会比公平性锁的效率要搞很多原因,不需要通知等待。
  • ReentrantLock 可重入锁通常两类:公平性、非公平性
  1. 公平性:按照线程请求资源的顺序依次获取,内部有一个计数器,如果当前线程没有释放资源再次访问,则无需排队,直接访问,计数器+1,当当前线程释放资源,按照请求顺序通知下一个线程获取资源。
  2. 非公平性:当前线程释放资源后,其他线程通过竞争来获取请求资源的资格,竞争失败的继续阻塞。
ConcurrentHashMap底层结构:jdk1.7与1.8的区别(面试常问
  • jdk1.7中采用Segment + HashEntry的方式进行实现
    在这里插入图片描述
    说白了就是数组(不太准确,但可以这么理解)+链表的结构。获取元素需要定位两次,第一次定位到Segment ,第二次定位到链表头

  • jdk1.8采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作。

jdk1.8时,去掉了Segment 这一层,也就是说不再是每个Segment 加锁,而是真正采用了数组+链表+红黑树的结构,由于其put操作增加synchronized修饰,并且采用了CAS控制插入、删除等操作,确保了其线程的安全。(初始依旧是数组+链表,当链表元素个数达到8时,会转变成红黑树。当元素只有6个时,再抓换回链表

为什么1.8不直接使用红黑树结构,还要保留链表结构呢?

首先,在数据较少的时候,红黑树和链表的查询效率是查不到的,但是,红黑树需要左旋,右旋等操作,其node节点的内存比较链表的node节点内存大小,要比起大将近一倍,所以,在数据量较少的时候采用链表,节省内存,并且插入操作不需要进行左旋、右旋。数据量较大时,采用红黑树,提高搜索效率。

3、volatile关键字

  1. volatile关键字保证了多线程的可见性,每次其他线程读取volatile关键字所修饰的变量时,都能读取到该变量最新的那个值。但是,volatile只能保证可见性,无法保证其原子性,不能说其修饰过就是线程安全的。
  2. volatitle关键字能保证多线程时,对其修饰的数据禁止指令重排序,当然,其性能会较其他稍稍逊色一些。(实现原理:内存屏障 ----- lock: *指令)
  • Java语言规范JVM线程内部维持顺序花语义,即只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码逻辑顺序不一致,这个过程就叫做指令的重排序。
  • 指令重排序的意义:使指令更加符合CPU的执行特性,最大限度的发挥机器的性能,提高程序的执行效率。

4、AQS(AbstractQueuedLongSynchronizer)

JUC(并发编程包)是基于AQS实现的,AQS是一个同步器,设计模式是模板模式。
核心数据结构:双向链表 + state(同步状态)
底层操作:CAS

AQS中的int类型的state值,这里就是通过CAS(乐观锁)去修改state的值。lock的基本操作还是通过乐观锁来实现的。

5、synchronized实现原理

MonitorEnter:进入同步块指令
MonitorExit:同步块结束指令

当获取到synchronized资源时,先获取Monitor类所有权,然后进行操作,结束后释放所有权。
被synchronized修饰的类,在转换为字节码文件时,其对象头MarkWord区域存放锁的信息。没有修饰时,其存放的是对象的HashCode信息。
目前java1.6以后,官方对synchronized做了大量的锁优化(偏向锁、自旋、轻量级锁)

6、CAS操作

  • CAS是compare and swap的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,性能较悲观锁有很大的提高。(c++层次swap方法)
  • CAS 操作包含三个操作数 ——内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
缺点:
  1. ABA问题:如果有的线程转换速度很快,把资源的值改变后又做了某些操作导致其值的大小和原来一样,这样其他线程再读的时候,发现值没变,直接读取。但是读取的值不是原来的值,只是数值一样。
    解决方法:加版本号
  2. 进程竞争激烈时可能发生自选时间过长,导致CPU开销很大,性能很低

7、MySQL索引类型和区别

  • normal:表示普通索引,单列索引
  • unique:表示唯一的,不允许重复的索引,可以为空
  • full textl: 表示 全文搜索的索引。FULLTEXT 用于搜索很长一篇文章的时候,效果最好。用在比较短的文本,如果就一两行字的,普通的 INDEX 也可以

MySQL目前主要有以下几种索引方法:B-Tree,Hash,R-Tree。

8、事务的四大特性

ACID:

⑴ 原子性(Atomicity)
  原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
⑵ 一致性(Consistency)
  一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
⑶ 隔离性(Isolation)
  隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
⑷ 持久性(Durability)
  持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

9、事务的隔离级别

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

事务隔离级别脏读不可重复读幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

10、sql优化

sql优化包含很多方面的内容,在此我们只针对sql语句以及表操作进行优化【引用文章链接】:

  1. 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
  2. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描

如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0

  1. 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描
  2. 应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描

如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20

  1. in 和 not in 也要慎用,否则会导致全表扫描

如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3

  1. 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。

如:
select id from t where num/2=100
应改为:
select id from t where num=100*2

  1. 应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。

如:
select id from t where substring(name,1,3)=‘abc’–name以abc开头的id
应改为:
select id from t where name like ‘abc%’

  1. 不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
  2. 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
  3. 不要写一些没有意义的查询

如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)

  1. 很多时候用 exists 代替 in 是一个好的选择

select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)

  1. 并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
  2. 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
  3. 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
  4. 尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
  5. 任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
  6. 避免频繁创建和删除临时表,以减少系统表资源的消耗。
  7. 临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
  8. 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
  9. 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
  10. 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

新手小白,经验不足,各位大佬,还望指教😁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值