挑战面试题一

原文转载自 「个人博客」 ( http://yoursite.com/2020/02/13/挑战面试题一/ ) By 爱写代码的小书童

预计阅读时间 0 分钟(共 0 个字, 0 张图片, 0 个链接)

所有面试题都是从网上收集的,知识面比较广,可能有重复的题目。所有的答案都是我自己查阅资料加上自己的理解所写的,难免有不详之处,希望大家不吝赐教!

volatile的底层如何实现,怎么就能保住可见性了?

volatile关键字修饰的变量可以确保内存可见性,禁止指令重排序。
volatile修饰的变量不允许线程内存cache缓存和重排序,线程读取数据的时候直接读取内存,同时volatile不会对变量加锁,因此性能会比synchronized好。
还有一种说法是:读取一个volatile关键字会使相应的处理器执行刷新处理器缓存的动作,写一个volatile关键字会使得相应的处理器执行冲刷处理器缓存的动作。

拓展:
假如我们有一个使用volatile的变量instance.有如下代码:

1
instance = new Singleton();

它的汇编代码如下:

1
2
0x01a3de1d: movb $0x0,0x1104800(%esi);
0x01a3de24: lock addl $0x0,(%esp); //使用volatile修饰的变量会多这行

使用lock前缀的指令会引发两件事情:

线程池有哪些创建方式

线程池的创建方式有两种:

有哪些线程池的类型

线程池中LinkedBlockingQueue满了的话,线程会怎么样

需要分两种情况:

  1. 如果当前运行的线程数大于核心线程数且小于最大线程数,那么就会新建线程。
  2. 如果当前运行的线程数已经达到了最大线程数,那么就会执行拒绝策略。

拓展:
JDK内置了4种拒绝策略,我们也可以继承RejectedExcutionHandler实现自己的拒绝策略。
内置的4种拒绝策略分别是:

线程池的底层原理和实现方法

在线程池中每个线程都被封装成为了一个Worker对象。在线程池内部比较重要的两个属性是一个存放所有线程的workers:HashSet,和阻塞队列。当有任务提交的时候,首先会判断当前线程数是否小于核心线程数,如果当前线程数小于核心线程数,那么就创建一个新的线程(将任务和线程封装为一个Worker)并运行线程,否则将任务添加到等待队列,等待工作线程依次从队列中取出任务执行。如果等待队列已满且工作线程数已经达到最大线程数了,那么就会执行拒绝策略。

线程之间的交互方式有哪些?

线程之间的通信方式有4种:

Java锁机制,都说一下~

Java提供了两种枷锁的方法:

拓展:
synchronized关键字的实现原理是通过JVM进入、退出对象监视器(Monitor)来实现对方法、同步块的同步的,而对象监视器的本质是依赖于操作系统的互斥锁Mutex Lock 实现的。从jdk1.6之后,对sychronized关键字进行了优化,为了能够减少获取和释放锁的消耗引入了偏向锁和轻量级锁。

引入偏向锁是为了避免在无多线程竞争的条件下的轻量级锁的执行路径。
偏向锁的获取过程:

  1. 判断对象头的Mark Word标志是否处于偏向锁状态。
  2. 如果为偏向锁状态,继续判断线程ID是否指向当前线程。如果线程ID指向当前线程ID,则跳到5)
  3. 如果线程ID没有指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将线程ID设置为当前线程ID。
  4. 如果竞争失败,则等待到达全局安全点时,得到偏向锁的线程就会挂起,偏向锁升级为轻量级锁,然后被阻塞的线程继续执行。
  5. 执行同步代码。

偏向锁的释放过程
偏向锁只由在遇到其它线程竞争偏向锁时,持有偏向锁的线程才会释放锁。

提出轻量级锁的目的是在没有多少线程竞争的前提下,减少传统的重量级锁锁使用所产生的性能消耗。

轻量级锁的加锁过程:

  1. 首先通过对象头的mark word字段判断是否是无锁状态,如果是无锁状态,那么虚拟机会在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的Mark Word的拷贝。
  2. 拷贝对象头中的mark word到锁记录中。
  3. 虚拟机尝试通过CAS将对象的Mark Word更新为指向当前线程的锁记录指针,并将当前线程的锁记录中的owner指向mark word。
  4. 如果更新成功,表明当前线程获得了该对象的锁,并且设置对象的Mark Word的为轻量级锁锁定状态。
  5. 如果更新失败,虚拟机会检查对象的mark word是否指向当前线程的栈帧,如果是则说明当前线程已经拥有了对象的锁。否则说明存在多个线程竞争锁,轻量级锁就升级为重量级锁(使用互斥量),修改锁标志位重量级锁,mark word中存储了指向互斥量的指针,后面等待的线程进入阻塞状态,当前线程使用自旋来获取锁。

并且在1.6之后,还增加了自适应自旋,减少了CAS操作时自旋的成本。即如果某个锁自旋很少就成功获得,那么下一次就会减少自旋。

编译器还会进行锁消除优化,消除没有必要的加锁。
我们在编写代码的时候,尽量减少同步的范围,即锁粗化

而显示Lock都是基于AQS实现的。比如:ReentrantLock。关于AQS我在另一篇博客中有详细介绍AQS

除了@ResponseBody,controller层如何标准返回给前端所要的数据类型?你会怎么实现?

还可以自定义消息转化器,通过继承AbstractHttpMessageConverter重写其中的方法,并注入的Spring容器即可。

目前只想到了这一种

异常捕获处理

Java异常Throwable分为两类,一类是Error,一类是Exception。Error是编程时需要避免的,程序无法处理的。而Exception就是我们平常需要处理异常。除了Exception下的RunTimeException及其子类都是可查异常,即编译器要求必须捕获的异常。
对于异常有三种处理方案:
一种是通过try,catch进行捕获,一种是定义方法的时候通过throws关键字声明,表明该方法不处理该异常,交由调用方处理。最后一种就是通过throw关键字抛出异常。

Redis的缓存淘汰策略有哪些?

Redis的缓存淘汰策略有6种,它们分别是:

Java内存模型说一下

Java内存分为:虚拟机栈,本地方法栈,程序计数器,方法区,堆区。

在JVM启动的时候首先会分配好方法区和堆区。程序计数器,虚拟机栈,本地方法栈都是线程私有的。

mybatis如何进行类型转换

Mybatis的

mybatis的xml有什么标签

定义sql语句的:insert,delete,updata,select
结果集映射的:resultMap
动态sql:foreach,if,choose
格式化输出:where,set,trim
配置关联关系:collection,association
定义sql片段:sql

MySQL锁机制

以我们平常经常使用的innoDB存储引擎为例,主要有七种类型的锁: 共享/排他锁,意向锁,记录锁,间隙锁,临键锁,插入意向锁,自增锁。
https://www.cnblogs.com/volcano-liu/p/9890832.html

如何修改linux的文件权限

修改文件的权限可以使用chmod命令。
比如:设置text.txt所有人可读
chmod ugo+r file.txt
u表示文件拥有者,g表示与文件的拥有者同一组人,o表示其它以外的人,a表示所有人。
+,-表示增加,取消权限,=表示唯一设定
r读,w写,x可执行

jvm的回收算法

标记清除算法,标记整理算法,复制算法,分代收集算法。
标记清除算法首先标记需要回收的对象,在标记完成后同一回收。
标记整理算法首先标记出需要回收的对象,然后让存活的对象向一端移动,最后清除掉边界以外的部分。
复制算法将内存划分为大小相等的两个部分,每次只使用其中一部分,内存回收时将存活的对象复制到另一个部分,然后再把使用过的一半内存清理掉。
分代收集算法根据对象不同的存活周期将内存划分为几块,一般划分为新生代和老年代。然后再根据不同分代对象的特点选用不同的回收算法。

数据库有哪些索引?

以我们常见的mysql为例,有5种索引。它们分别是主键索引,唯一索引,普通索引,全文索引,联合索引。

拓展:
当我们为表设置主键的时候,则该类就是主键索引。当然也可以在建表之后添加:
alter table table_name add primary key (colum_name)

普通索引一般是在建表后再添加:
alter table table_name add index index_name(colum_name1,colum_name2)

全文索引主要针对文本文件,在mysql5.6之前只有MyISAM支持,后来InnoDB也支持全文索引。

唯一索引可以有多个空值,但是不能重复。相比于主键,主键不能重复也不能有空值。

如何防止sql注入

使用预编译语句避免拼接sql,限制数据库权限,避免直接向用户显示错误信息

抽象类和接口有什么不同

mysql间歇锁的实现原理

间歇锁是innodb在可重复读提交下为了解决幻读问题而引入的锁机制,它是一种行锁。间隙锁会锁定一个范围但是不包含记录本身。
间隙锁会根据检索添加向左寻找最靠近检索条件的记录值A,作为左区间,向右寻找最靠近检索添加的记录值B作为右区间,即锁定的间隙为[A,记录),(记录,B]。
https://www.jianshu.com/p/32904ee07e56

future的底层实现异步原理

Future表示异步的计算结果,它提供了检查计算结果是否完成的方法以及等待计算的完成,并获取计算的结果。计算完成后可以通过get方法获取,如果有必要可以阻塞该方法知道计算完成。

多线程讲一下,FutureTask

Future表示一个任务的生命周期,是一个可取消的异步运算。FutureTask提供了Future的最基础的实现。它的内部有一个Callable任务,一个执行任务的线程,一个保存等待线程的栈,和一个用于保存结果或异常的Object对象.当调用get方法获取结果的时,如果任务未完成就假如等待栈中,知道计算完毕后唤醒。

乐观锁和悲观锁

悲观锁采用“先取锁再访问”的保守策略,使用悲观锁会增加数据库的开销,还带来了死锁的分享。而乐观锁不会刻意去依赖锁,而是利用数据本身来保证数据恶的正确性。乐观锁的一种常见实现就是通过给数据加上版本号,类似CAS的思想。

more_vert