所有面试题都是从网上收集的,知识面比较广,可能有重复的题目。所有的答案都是我自己查阅资料加上自己的理解所写的,难免有不详之处,希望大家不吝赐教!
volatile的底层如何实现,怎么就能保住可见性了?
volatile关键字修饰的变量可以确保内存可见性,禁止指令重排序。volatile修饰的变量不允许线程内存cache缓存和重排序,线程读取数据的时候直接读取内存,同时volatile不会对变量加锁,因此性能会比synchronized好。
还有一种说法是:读取一个volatile关键字会使相应的处理器执行刷新处理器缓存的动作,写一个volatile关键字会使得相应的处理器执行冲刷处理器缓存的动作。
拓展:
假如我们有一个使用volatile的变量instance.有如下代码:
1 | instance = new Singleton(); |
它的汇编代码如下:
1 | 0x01a3de1d: movb $0x0,0x1104800(%esi); |
使用lock前缀的指令会引发两件事情:
- 将当前处理器缓存含的数据写回到系统内存
- 这个写回内存的操作回引起在其它CPU里缓存了该内存地址的数据无效
线程池有哪些创建方式
线程池的创建方式有两种:
- 使用
ThreadPoolExecutor类创建。使用该方法创建时,参数比较复杂可以参考ThreadPoolExecutor源码分析 - 使用
Executor类。具体的创建方法可参考下一个问题。
有哪些线程池的类型
ExecutorService fixedExecutor = Executors.newFixedThreadPool(3);
定长线程池,每当提交一个任务就创建一个线程,知道达到线程池的最大数量,这时线程数量不再变化,当线程发送错误结束时,线程池会补充一个新的线程。ExecutorService cachedExecutor = Executors.newCachedThreadPool();
可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制。ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(3);
定长线程池,可执行周期性的任务ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
单线程线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行ScheduledExecutorService singleScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
单线程可执行周期性任务的线程池。ExecutorService workStealingExecutor = Executors.newWorkStealingPool();
任务窃取线程池,不保证执行顺序,适合任务耗时差异较大。
线程池中LinkedBlockingQueue满了的话,线程会怎么样
需要分两种情况:
- 如果当前运行的线程数大于核心线程数且小于最大线程数,那么就会新建线程。
- 如果当前运行的线程数已经达到了最大线程数,那么就会执行拒绝策略。
拓展:
JDK内置了4种拒绝策略,我们也可以继承RejectedExcutionHandler实现自己的拒绝策略。
内置的4种拒绝策略分别是:
AbortPolicy:直接抛出异常(默认)CallerRunsPolicy:直接在execute方法的调用线程中运行拒绝的任务;如果执行程序已关闭,则会丢弃该任务。DiscardPolicy:直接丢弃任务。DiscardOldestPolicy:丢弃队列中等待时间最长的任务(即队头),然后运行当前的任务。
线程池的底层原理和实现方法
在线程池中每个线程都被封装成为了一个Worker对象。在线程池内部比较重要的两个属性是一个存放所有线程的workers:HashSet,和阻塞队列。当有任务提交的时候,首先会判断当前线程数是否小于核心线程数,如果当前线程数小于核心线程数,那么就创建一个新的线程(将任务和线程封装为一个Worker)并运行线程,否则将任务添加到等待队列,等待工作线程依次从队列中取出任务执行。如果等待队列已满且工作线程数已经达到最大线程数了,那么就会执行拒绝策略。
线程之间的交互方式有哪些?
线程之间的通信方式有4种:
- 共享变量
- wait/notify机制
- Lock/Condition机制
- 管道
Java锁机制,都说一下~
Java提供了两种枷锁的方法:
- synchronized关键字
- 显示Lock
拓展:
synchronized关键字的实现原理是通过JVM进入、退出对象监视器(Monitor)来实现对方法、同步块的同步的,而对象监视器的本质是依赖于操作系统的互斥锁Mutex Lock 实现的。从jdk1.6之后,对sychronized关键字进行了优化,为了能够减少获取和释放锁的消耗引入了偏向锁和轻量级锁。
引入偏向锁是为了避免在无多线程竞争的条件下的轻量级锁的执行路径。
偏向锁的获取过程:
- 判断对象头的Mark Word标志是否处于偏向锁状态。
- 如果为偏向锁状态,继续判断线程ID是否指向当前线程。如果线程ID指向当前线程ID,则跳到5)
- 如果线程ID没有指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将线程ID设置为当前线程ID。
- 如果竞争失败,则等待到达全局安全点时,得到偏向锁的线程就会挂起,偏向锁升级为轻量级锁,然后被阻塞的线程继续执行。
- 执行同步代码。
偏向锁的释放过程
偏向锁只由在遇到其它线程竞争偏向锁时,持有偏向锁的线程才会释放锁。
提出轻量级锁的目的是在没有多少线程竞争的前提下,减少传统的重量级锁锁使用所产生的性能消耗。
轻量级锁的加锁过程:
- 首先通过对象头的mark word字段判断是否是无锁状态,如果是无锁状态,那么虚拟机会在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的Mark Word的拷贝。
- 拷贝对象头中的mark word到锁记录中。
- 虚拟机尝试通过CAS将对象的Mark Word更新为指向当前线程的锁记录指针,并将当前线程的锁记录中的owner指向mark word。
- 如果更新成功,表明当前线程获得了该对象的锁,并且设置对象的Mark Word的为轻量级锁锁定状态。
- 如果更新失败,虚拟机会检查对象的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种,它们分别是:
- noeviction:不删除策略,达到最大内存限制时,如果需要更多的内存,直接返回错误信息。
- allkeys-lru:对于所有的key优先删除最近最少使用的key。
- allkeys-random:对于所有的key,随机删除一部分。
- volatile-random:只限于设置了expore的部分,删除一部分expire的key
- volatile-ttl:只限于设置了expire的部分,优先删除剩余时间(TTL)短的key。
- volatile-lru:只限于色湖之了expore的部分,删除最近最少使用的key。
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,限制数据库权限,避免直接向用户显示错误信息
抽象类和接口有什么不同
- 抽象类是用来继承而接口是用来实现的。
- 接口中的所有方法都必须是抽象方法,而抽象类中即可以包含抽象方法也可以包含非抽象的方法。
- 接口中声明的成员变量都是final的,而抽象类则没有限制。
- 接口中的方法默认是public的,而抽象类方法的访问权限可以任意指定。
- 类可以实现多个接口,但是只能继承一个抽象类。
- 抽象类可以在不实现接口方法的情况下实现接口。
mysql间歇锁的实现原理
间歇锁是innodb在可重复读提交下为了解决幻读问题而引入的锁机制,它是一种行锁。间隙锁会锁定一个范围但是不包含记录本身。
间隙锁会根据检索添加向左寻找最靠近检索条件的记录值A,作为左区间,向右寻找最靠近检索添加的记录值B作为右区间,即锁定的间隙为[A,记录),(记录,B]。
https://www.jianshu.com/p/32904ee07e56
future的底层实现异步原理
Future表示异步的计算结果,它提供了检查计算结果是否完成的方法以及等待计算的完成,并获取计算的结果。计算完成后可以通过get方法获取,如果有必要可以阻塞该方法知道计算完成。
多线程讲一下,FutureTask
Future表示一个任务的生命周期,是一个可取消的异步运算。FutureTask提供了Future的最基础的实现。它的内部有一个Callable任务,一个执行任务的线程,一个保存等待线程的栈,和一个用于保存结果或异常的Object对象.当调用get方法获取结果的时,如果任务未完成就假如等待栈中,知道计算完毕后唤醒。
乐观锁和悲观锁
悲观锁采用“先取锁再访问”的保守策略,使用悲观锁会增加数据库的开销,还带来了死锁的分享。而乐观锁不会刻意去依赖锁,而是利用数据本身来保证数据恶的正确性。乐观锁的一种常见实现就是通过给数据加上版本号,类似CAS的思想。