Java面试题总结(乱序版,2020-08-31)

   日期:2020-09-05     浏览:81    评论:0    
核心提示:一、如何将字符串反转?二、String 类的常用方法都有那些?三、ArrayList 和 LinkedList 的区别是什么?四、请尽可能详尽地对比下 Synchronized 和 ReentrantLock 的异同。五、ReentrantLock 是如何实现可重入性的?六、简述 tcp 和 udp的区别?七、在 Java 中,什么叫观察者设计模式(observer design pattern)?八、请解释 Spring Bean 的生命周期?九、Spring Bean 的

一、如何将字符串反转?

添加到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类提供了一些高级功能,主要有如下三项:

  1. 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized避免出现死锁的情况。通过lock.lockInterruptibly()来实现这一机制;
  2. 公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁是非公平锁;ReentrantLock默认也是非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好;
  3. 锁绑定多个条件,一个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、代码分析

  1. 当一个线程在获取锁过程中,先判断state的值是否为0,如果是表示没有线程持有锁,就可以尝试获取锁。
  2. 当state的值不为0时,表示锁已经被一个线程占用了,这时会做一个判断current==getExclusiveOwnerThread(),这个方法返回的是当前持有锁的线程,这个判断是看当前持有锁的线程是不是自己,如果是自己,那么将state的值+1,表示重入返回即可。

六、简述 tcp 和 udp的区别?

  1. TCP是传输控制协议,UDP是用户数据表协议;
  2. TCP长连接,UDP无连接;
  3. UDP程序结构较简单,只需发送,无须接收;
  4. TCP可靠,保证数据正确性、顺序性;UDP不可靠,可能丢数据;
  5. TCP适用于少量数据,UDP适用于大量数据传输;
  6. 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 的生命周期?

  1. 通过构造器或工厂方法创建bean实例;
  2. 为bean的属性赋值;
  3. 调用bean的初始化方法;
  4. 使用bean;
  5. 当容器关闭时,调用bean的销毁方法;

九、Spring Bean 的作用域之间有什么区别?

Spring容器中的bean可以分为5个范围:

  1. singleton:这种bean范围是默认的,这种范围确保不管接受多少请求,每个容器中只哟一个bean的实例,单例模式;
  2. prototype:为每一个bean提供一个实例;
  3. request:在请求bean范围内为每一个来自客户端的网络请求创建一个实例,在请求完毕后,bean会失效并被垃圾回收器回收;
  4. session:为每个session创建一个实例,session过期后,bean会随之消失;
  5. 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使用的注意事项:

  1. 在properties文件中是以"."进行分割的,在yml中是用"."进行分割的;
  2. yml的数据格式和json的格式很像,都是K-V格式,并且通过":"进行赋值;
  3. 每个冒号后面一定要加一个空格;

十二、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查询语句。

它具备以下功能:

  1. 在查询语句中设定各种查询条件;
  2. 支持投影查询,即仅检索出对象的部分属性;
  3. 支持分页查询;
  4. 支持连接查询;
  5. 支持分组查询;
  6. 提供内置函数;
  7. 能够调用用户自定义的SQL函数;
  8. 支持子查询;
  9. 支持动态绑定参数;

例如:

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、如何选择

  1. mybatis需要编写sql和映射规则,工作量大于hibernate;
  2. mybatis支持的工具也有限,不能像hibernate那样有许多插件可以帮助生成映射代码和关联关系;
  3. 对于性能要求不太苛刻的系统,比如管理系统、ERP等推荐hibernate;
  4. 对于性能要求高、响应快、灵活的系统,比如电商系统,推荐使用mybatis;

十四、说一下 ACID 是什么?

ACID是数据库事务执行的四大基本要素,包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

1、原子性

整个事务中的所有操作,要么全部完成,要不全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被roolback回滚到事务开始前的状态,就像这个事务从未执行过一样。

2、一致性

事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。

3、隔离性

隔离状态执行事务,使他们好像是系统在给定时间内执行的唯一操作。

如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性确保每一个事务在系统中认为只有自己在使用系统。这种属性称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。

4、持久性

一个成功的事务将永久的改变系统的状态。

十五、Redis为什么是单线程的?

  1. 代码更清晰,处理逻辑更简单;
  2. 不用考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现死锁而导致的性能问题;
  3. 不存在多线程切换而消耗CPU;
  4. 无法发挥多核CPU的优势,但可以采用多开几个Redis实例来完善;

十六、说一下 jvm 有哪些垃圾回收算法?

1、对象是否已死算法

  • 引用计数器算法
  • 可达性分析算法

2、GC算法

(1)标记清除算法

如果对象被标记后进行清除,会带来一个新的问题--内存碎片化。如果下次有比较大的对象实例需要在堆上分配较大的内存空间时,可能会出现无法找到足够的连续内存而不得不再次触发垃圾回收。

(2)复制算法(Java堆中新生代的垃圾回收算法)

① 先标记待回收内存和不用回收内存;

② 将不用回收的内存复制到新的内存区域;

③ 就的内存区域就可以被全部回收了,而新的内存区域也是连续的;

缺点是损失部分系统内存,因为腾出部分内存进行复制。

(3)标记压缩算法(Java堆中老年代的垃圾回收算法)

对于新生代,大部分对象都不会存活,所以复制算法较高效,但对于老年代,大部分对象可能要继续存活,如果此时使用复制算法,效率会降低。

标记压缩算法首先还是标记,将不用回收的内存对象压缩到内存一端,此时即可清除边界处的内存,这样就能避免复制算法带来的效率问题,同时也能避免内存碎片化的问题。

老年代的垃圾回收算法称为“Major GC”。

 

上一篇:Java面试题总结(乱序版,2020-08-20)

下一篇:Java面试题总结(绝对经典)

 

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服