一、如何将字符串反转?
添加到StringBuilder中,然后调用reverse()。
二、String 类的常用方法都有那些?
equals、length、contains、replace、split、hashcode、indexof、substring、trim、toUpperCase、toLowerCase、isEmpty等等。
三、ArrayList 和 LinkedList 的区别是什么?
ArrayList是动态数组的数据结构实现,查找和遍历的效率较高;
LinkedList 是双向链表的数据结构,增加和删除的效率较高;
四、请尽可能详尽地对比下 Synchronized 和 ReentrantLock 的异同。
1、相似点
它们都是阻塞式的同步,也就是说一个线程获得了对象锁,进入代码块,其它访问该同步块的线程都必须阻塞在同步代码块外面等待,而进行线程阻塞和唤醒的代码是比较高的。
2、功能区别
- Synchronized是java语言的关键字,是原生语法层面的互斥,需要JVM实现;ReentrantLock 是JDK1.5之后提供的API层面的互斥锁,需要lock和unlock()方法配合try/finally代码块来完成。
- Synchronized使用较ReentrantLock 便利一些;
- 锁的细粒度和灵活性:ReentrantLock强于Synchronized;
3、性能区别
Synchronized引入偏向锁,自旋锁之后,两者的性能差不多,在这种情况下,官方建议使用Synchronized。
(1)Synchronized
Synchronized会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。
在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计数器+1,相应的执行monitorexit时,计数器-1,当计数器为0时,锁就会被释放。如果获取锁失败,当前线程就要阻塞,知道对象锁被另一个线程释放为止。
(2)ReentrantLock
ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有如下三项:
- 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized避免出现死锁的情况。通过lock.lockInterruptibly()来实现这一机制;
- 公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁是非公平锁;ReentrantLock默认也是非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好;
- 锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个对象。ReentrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像Synchronized要么随机唤醒一个线程,要么唤醒全部线程。
五、ReentrantLock 是如何实现可重入性的?
1、什么是可重入性
一个线程持有锁时,当其他线程尝试获取该锁时,会被阻塞;而这个线程尝试获取自己持有锁时,如果成功说明该锁是可重入的,反之则不可重入。
2、synchronized是如何实现可重入性
synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。每个锁对象内部维护一个计数器,该计数器初始值为0,表示任何线程都可以获取该锁并执行相应的方法。根据虚拟机规范要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了对象的锁,把锁的计数器+1,相应的在执行monitorexit指令后锁计数器-1,当计数器为0时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。
3、ReentrantLock如何实现可重入性
ReentrantLock使用内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。Sync有两个实现,分别为NonfairSync(非公公平锁)和FairSync(公平锁)。Sync通过继承AQS实现,在AQS中维护了一个private volatile int state来计算重入次数,避免频繁的持有释放操作带来的线程问题。
4、ReentrantLock代码实例
// Sync继承于AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
...
}
// ReentrantLock默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 可以通过向构造方法中传true来实现公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
protected final boolean tryAcquire(int acquires) {
// 当前想要获取锁的线程
final Thread current = Thread.currentThread();
// 当前锁的状态
int c = getState();
// state == 0 此时此刻没有线程持有锁
if (c == 0) {
// 虽然此时此刻锁是可以用的,但是这是公平锁,既然是公平,就得讲究先来后到,
// 看看有没有别人在队列中等了半天了
if (!hasQueuedPredecessors() &&
// 如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了,
// 不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了 =_=
// 因为刚刚还没人的,我判断过了
compareAndSetState(0, acquires)) {
// 到这里就是获取到锁了,标记一下,告诉大家,现在是我占用了锁
setExclusiveOwnerThread(current);
return true;
}
}
// 会进入这个else if分支,说明是重入了,需要操作:state=state+1
// 这里不存在并发问题
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 如果到这里,说明前面的if和else if都没有返回true,说明没有获取到锁
return false;
}
5、代码分析
- 当一个线程在获取锁过程中,先判断state的值是否为0,如果是表示没有线程持有锁,就可以尝试获取锁。
- 当state的值不为0时,表示锁已经被一个线程占用了,这时会做一个判断current==getExclusiveOwnerThread(),这个方法返回的是当前持有锁的线程,这个判断是看当前持有锁的线程是不是自己,如果是自己,那么将state的值+1,表示重入返回即可。
六、简述 tcp 和 udp的区别?
- TCP是传输控制协议,UDP是用户数据表协议;
- TCP长连接,UDP无连接;
- UDP程序结构较简单,只需发送,无须接收;
- TCP可靠,保证数据正确性、顺序性;UDP不可靠,可能丢数据;
- TCP适用于少量数据,UDP适用于大量数据传输;
- TCP速度慢,UDP速度快;
七、在 Java 中,什么叫观察者设计模式(observer design pattern)?
1、观察者模式是一种一对多的依赖关系,让多个观察者同时监听某一主题对象。当这个主题对象发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
2、JAVA提供的对观察者模式的支持
在JAVA语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。
- Observer接口
这个接口只定义了一个方法,即update()方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()方法就会调用这一方法。
public interface Observer {
void update(Observable o, Object arg);
}
- Observable类
被观察者类都是java.util.Observable类的子类。java.util.Observable提供公开的方法支持观察者对象,这些方法中有两个对Observable的子类非常重要:一个是setChanged(),另一个是notifyObservers()。第一方法setChanged()被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。第二个是notifyObservers(),这个方法被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。
八、请解释 Spring Bean 的生命周期?
- 通过构造器或工厂方法创建bean实例;
- 为bean的属性赋值;
- 调用bean的初始化方法;
- 使用bean;
- 当容器关闭时,调用bean的销毁方法;
九、Spring Bean 的作用域之间有什么区别?
Spring容器中的bean可以分为5个范围:
- singleton:这种bean范围是默认的,这种范围确保不管接受多少请求,每个容器中只哟一个bean的实例,单例模式;
- prototype:为每一个bean提供一个实例;
- request:在请求bean范围内为每一个来自客户端的网络请求创建一个实例,在请求完毕后,bean会失效并被垃圾回收器回收;
- session:为每个session创建一个实例,session过期后,bean会随之消失;
- global-session:global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet公用全局的存储变量的话,那么全局变量需要存储在global-session中。
十、如何在 Spring Boot 中禁用 Actuator 端点安全性?
默认情况下,所有敏感的HTTP端点都是安全的,只有具有Actuator角色的用户才能访问它们。安全性是使用标准的HTTPServletRequest.isUserInRole方法实施的。我们可以使用management.security.enable = false来禁用安全性。只有在执行机构端点在防火墙后访问时,才建议禁用安全性。
十一、什么是 YAML?
YAML是JSON的一个超集,可以非常方便地将外部配置以层次结构形式存储起来。YAML可以作为properties配置文件的替代。
YAML使用的注意事项:
- 在properties文件中是以"."进行分割的,在yml中是用"."进行分割的;
- yml的数据格式和json的格式很像,都是K-V格式,并且通过":"进行赋值;
- 每个冒号后面一定要加一个空格;
十二、hibernate 有几种查询方式?
1、导航对象图查询:根据已加载的对象,导航到其他对象。
例如,对于已经加载的Customer对象,调用它的getOrders().iterator()方法就可以导航到所有关联的Order对象,假如在关联级别使用了延迟加载检索策略,那么首次执行此方法时,hibernate会从数据库中加载关联的Order对象,否则就从缓存中获得Order对象。
2、OID方式:按照对象的OID来检索对象
Session的get()和load()方法提供了这种功能,如果在应用程序中先知道了OID,就可以使用这种方式检索对象。
get()和load()的用法完全一样,都需要两个参数,一个是持久化对象类名class,一个是行号OID,返回固定的某一行的数据,但是需要注意的是,当输入的OID不存在时,get()会返回一个空对象,load()则直接报错。
3、HQL检索方式:(hibernate query language)
使用面向对象的HQL查询语言,session的find()方法用于执行HQL查询语句。此外,hibernate还提供了query接口,它是hibernate提供的专门的HQL查询接口,能够执行各种复杂的HQL查询语句。
它具备以下功能:
- 在查询语句中设定各种查询条件;
- 支持投影查询,即仅检索出对象的部分属性;
- 支持分页查询;
- 支持连接查询;
- 支持分组查询;
- 提供内置函数;
- 能够调用用户自定义的SQL函数;
- 支持子查询;
- 支持动态绑定参数;
例如:
Query query = session.createQuery(“from UserPo”);
获得一个query对象,注意参数字符串中不是一个SQL语句,from后面的是持久化对象名称;
List list = query.list();
就可以获取数据库中对应表的数据集合。
4、QBC检索方式:Query By Criteria的API来检索对象
这种API封装了基于字符串形式的查询语句,提供了更加面向对象的接口。
例:Criteria criteria = session.createCriteria(UserPo.class);
创建一个Criteria对象,参数是所关联的持久化对象,criteria.add(Restrictions.ge("id",2));将查询条件加入对象中,后面的操作就和Query对象一样了。
5、本地SQL
使用本地数据库的SQL查询语句,hibernate会负责把检索到的JDBC ResultSet结果映射为持久化对象图。
十三、mybatis 和 hibernate 的区别有哪些?
1、两者最大的区别
针对简单逻辑,都有对应的代码生成工具,可以生成简单基本的dao层方法;
针对高级查询,mybatis要手动编写sql语句和resultMap,而hibernate有良好的映射机制;
2、开发难度对比
hibernate > mybatis
3、日志统计
hibernate有自己的日志统计功能,而mybatis需要借助log4j来记录日志。
4、数据库扩展比较
hibernate > mybatis
5、缓存机制比较
因为hibernate对查询对象有良好的管理机制,用户无需关心sql,所以使用二级缓存如果出现脏数据,系统会报错。
而mybatis,如果不能获取最新数据,应该避免缓存的使用,脏数据的出现会给系统的正常运行带来很大的隐患。
6、如何选择
- mybatis需要编写sql和映射规则,工作量大于hibernate;
- mybatis支持的工具也有限,不能像hibernate那样有许多插件可以帮助生成映射代码和关联关系;
- 对于性能要求不太苛刻的系统,比如管理系统、ERP等推荐hibernate;
- 对于性能要求高、响应快、灵活的系统,比如电商系统,推荐使用mybatis;
十四、说一下 ACID 是什么?
ACID是数据库事务执行的四大基本要素,包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
1、原子性
整个事务中的所有操作,要么全部完成,要不全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被roolback回滚到事务开始前的状态,就像这个事务从未执行过一样。
2、一致性
事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。
3、隔离性
隔离状态执行事务,使他们好像是系统在给定时间内执行的唯一操作。
如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性确保每一个事务在系统中认为只有自己在使用系统。这种属性称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。
4、持久性
一个成功的事务将永久的改变系统的状态。
十五、Redis为什么是单线程的?
- 代码更清晰,处理逻辑更简单;
- 不用考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现死锁而导致的性能问题;
- 不存在多线程切换而消耗CPU;
- 无法发挥多核CPU的优势,但可以采用多开几个Redis实例来完善;
十六、说一下 jvm 有哪些垃圾回收算法?
1、对象是否已死算法
- 引用计数器算法
- 可达性分析算法
2、GC算法
(1)标记清除算法
如果对象被标记后进行清除,会带来一个新的问题--内存碎片化。如果下次有比较大的对象实例需要在堆上分配较大的内存空间时,可能会出现无法找到足够的连续内存而不得不再次触发垃圾回收。
(2)复制算法(Java堆中新生代的垃圾回收算法)
① 先标记待回收内存和不用回收内存;
② 将不用回收的内存复制到新的内存区域;
③ 就的内存区域就可以被全部回收了,而新的内存区域也是连续的;
缺点是损失部分系统内存,因为腾出部分内存进行复制。
(3)标记压缩算法(Java堆中老年代的垃圾回收算法)
对于新生代,大部分对象都不会存活,所以复制算法较高效,但对于老年代,大部分对象可能要继续存活,如果此时使用复制算法,效率会降低。
标记压缩算法首先还是标记,将不用回收的内存对象压缩到内存一端,此时即可清除边界处的内存,这样就能避免复制算法带来的效率问题,同时也能避免内存碎片化的问题。
老年代的垃圾回收算法称为“Major GC”。
上一篇:Java面试题总结(乱序版,2020-08-20)
下一篇:Java面试题总结(绝对经典)