面试官:欢迎你来面试,先简单介绍下你自己吧。
王铁牛:面试官您好,我叫王铁牛,有两年Java开发经验,熟悉一些常见的Java技术。
面试官:好,第一轮提问开始。首先,Java 中如何创建一个线程?
王铁牛:可以通过继承Thread类或者实现Runnable接口来创建线程。继承Thread类需要重写run方法,实现Runnable接口也需要实现run方法,然后通过Thread类的构造函数传入Runnable对象来创建线程。
面试官:回答得不错。那说说线程池的核心参数有哪些?
王铁牛:线程池的核心参数有corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程存活时间)、unit(时间单位)、workQueue(任务队列)、threadFactory(线程工厂)、handler(拒绝策略)。
面试官:嗯,掌握得挺扎实。最后一个问题,多线程中如何保证线程安全?
王铁牛:可以使用synchronized关键字、Lock接口、原子类等方式来保证线程安全。
面试官:好,第一轮提问结束。接下来进入第二轮提问。说说HashMap的底层实现原理。
王铁牛:HashMap底层是基于数组和链表(JDK 1.8后引入红黑树)实现的。当插入一个元素时,先通过哈希函数计算出元素的哈希值,然后根据哈希值找到对应的数组下标。如果该下标为空,则直接插入新元素;如果不为空,则通过链表或红黑树来存储新元素。
面试官:那ArrayList的扩容机制是怎样的?
王铁牛:ArrayList初始容量是10,当元素数量超过容量时,会进行扩容。扩容时新容量是原容量的1.5倍,如果新容量还不够,则直接新容量设为所需的大小。
面试官:不太准确。ArrayList扩容是新容量为原容量的1.5倍加1。再问个问题,Spring的核心特性有哪些?
王铁牛:Spring的核心特性有依赖注入、面向切面编程、IoC容器等。
面试官:第二轮提问完毕。现在是第三轮提问。说说MyBatis的工作原理。
王铁牛:MyBatis通过读取配置文件,解析SQL语句,然后通过反射机制创建对象,执行SQL操作,最后返回结果。
面试官:Dubbo的集群容错模式有哪些?
王铁牛:有Failover Cluster(失败自动切换)、Failfast Cluster(快速失败)、Failsafe Cluster(失败安全)、Failback Cluster(失败自动恢复)、Forking Cluster(并行调用多个服务)等。
面试官:回答得比较混乱。最后一个问题,Redis的数据结构有哪些?
王铁牛:有字符串、哈希、列表、集合、有序集合等。
面试官:好,面试结束。回去等通知吧。
答案:
- Java创建线程的方式:
- 继承Thread类:创建一个类继承Thread类,重写run方法,在run方法中编写线程执行的代码。然后通过创建该类的实例并调用start方法来启动线程。例如:
class MyThread extends Thread { @Override public void run() { System.out.println("线程执行"); } } MyThread thread = new MyThread(); thread.start();- 实现Runnable接口:创建一个类实现Runnable接口,实现其中的run方法。然后通过Thread类的构造函数将该类的实例传入,再调用start方法启动线程。例如:
class MyRunnable implements Runnable { @Override public void run() { System.out.println("线程执行"); } } MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); - 线程池的核心参数:
- corePoolSize(核心线程数):线程池创建时初始化的线程数,当提交的任务数小于corePoolSize时,线程池会创建新线程来执行任务。
- maximumPoolSize(最大线程数):线程池能够容纳的最大线程数,当任务数大于corePoolSize且任务队列已满时,会创建新线程直到线程数达到maximumPoolSize。
- keepAliveTime(线程存活时间):当线程数大于corePoolSize时,多余的线程在空闲时会存活的时间。
- unit(时间单位):keepAliveTime的时间单位。
- workQueue(任务队列):用于存放提交的任务,当线程池繁忙时,任务会被放入任务队列中。
- threadFactory(线程工厂):用于创建线程,可自定义线程的名称、优先级等属性。
- handler(拒绝策略):当线程池的线程数达到maximumPoolSize且任务队列已满时,新提交的任务会被拒绝,通过handler来处理拒绝的任务。常见的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(调用者运行任务)、DiscardPolicy(丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
- 多线程保证线程安全的方式:
- synchronized关键字:可以修饰方法或代码块。修饰方法时,同一时刻只能有一个线程访问该方法;修饰代码块时,同一时刻只能有一个线程进入该代码块。例如:
public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; } }- Lock接口:通过Lock接口的实现类如ReentrantLock来实现线程同步。可以更灵活地控制锁的获取和释放,还可以实现公平锁等特性。例如:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private int count = 0; private Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } }- 原子类:如AtomicInteger、AtomicLong等,它们提供了原子性的操作方法,不需要使用锁就可以保证操作的原子性。例如:
import java.util.concurrent.atomic.AtomicInteger; public class AtomicExample { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } } - HashMap的底层实现原理:
- HashMap底层基于数组和链表(JDK 1.8后引入红黑树)实现。
- 哈希计算:插入元素时,首先通过哈希函数计算元素的哈希值。
- 数组存储:根据哈希值通过取模运算得到数组下标,如果该下标为空,则直接将元素插入到该位置。
- 链表或红黑树存储:如果下标不为空,说明有元素冲突,此时会将新元素插入到链表的尾部(JDK 1.8前)或红黑树中(JDK 1.8后,当链表长度大于8且数组长度大于64时,链表会转换为红黑树)。
- 扩容机制:当HashMap中的元素个数超过容量*负载因子(默认0.75)时,会进行扩容。扩容时会创建一个新的更大的数组,将原数组中的元素重新计算哈希值后插入到新数组中。
- ArrayList的扩容机制:
- ArrayList初始容量是10。
- 当元素数量超过容量时,会进行扩容。扩容时新容量是原容量的1.5倍加1。
- 例如,原容量为10,当插入第11个元素时,新容量变为10 * 1.5 + 1 = 16。然后将原数组中的元素复制到新数组中。
- Spring的核心特性:
- 依赖注入(Dependency Injection):通过IoC容器将对象之间的依赖关系注入到对象中,降低了对象之间的耦合度。例如,一个Service类依赖于一个Dao类,通过Spring可以在配置文件中配置将Dao类的实例注入到Service类中。
- 面向切面编程(Aspect - Oriented Programming,AOP):可以将一些横切关注点(如日志记录、事务管理等)与业务逻辑分离,通过切面来实现这些功能。比如在方法执行前后记录日志,在业务方法上添加事务注解来实现事务管理。
- IoC容器:负责创建、配置和管理对象,管理对象之间的依赖关系,使得对象的创建和使用更加方便和灵活。
- MyBatis的工作原理:
- 读取配置文件:MyBatis首先读取配置文件,配置文件中包含了数据库连接信息、SQL映射等内容。
- 解析SQL语句:通过解析器将SQL映射文件中的SQL语句进行解析,将其封装成MappedStatement对象。
- 反射机制创建对象:利用反射机制根据配置信息创建相应的对象,如SqlSessionFactory、SqlSession等。
- 执行SQL操作:通过SqlSession对象来执行SQL语句,SqlSession会根据MappedStatement对象找到对应的SQL语句并执行,执行过程中会处理参数传递、结果集映射等。
- 返回结果:将SQL执行结果按照配置的结果集映射规则进行映射,返回给调用者。
- Dubbo的集群容错模式:
- Failover Cluster(失败自动切换):当调用失败时,会自动切换到其他服务提供者进行重试,默认重试次数是2次。适用于读操作等对一致性要求不高的场景。
- Failfast Cluster(快速失败):如果调用失败,直接抛出异常,不会进行重试。适用于幂等性操作,如新增数据时如果失败不希望重试。
- Failsafe Cluster(失败安全):调用失败时,直接忽略,不抛出异常,也不进行重试。适用于写审计日志等操作,即使失败也不影响业务流程。
- Failback Cluster(失败自动恢复):调用失败时,记录失败请求,然后定时重试。适用于消息通知等操作,允许一定时间内的失败并自动恢复。
- Forking Cluster(并行调用多个服务):同时调用多个服务提供者,只要有一个成功就返回,用于对实时性要求较高的场景,比如实时查询多个数据源获取数据。
- Redis的数据结构:
- 字符串(String):最基本的数据结构,能存储各种类型的数据,如整数、字符串等。可以用于缓存、计数器、分布式锁等场景。例如:
SET key value GET key INCR key // 自增操作- 哈希(Hash):适合存储对象,是一个键值对集合。可以将对象的属性和值存储在哈希中。例如:
HMSET user:1 name "张三" age 20 HGETALL user:1- 列表(List):按照插入顺序排序的列表,可以从列表两端进行操作。常用于消息队列、任务队列等场景。例如:
LPUSH mylist element1 element2 RPOP mylist- 集合(Set):无序的唯一元素集合。可以用于去重、交集、并集等操作。例如:
SADD myset element1 element2 SMEMBERS myset SINTER set1 set2 // 求交集- 有序集合(Sorted Set):集合中的元素是有序的,通过分数进行排序。可以用于排行榜等场景。例如:
ZADD myzset 10 element1 20 element2 ZRANGE myzset 0 -1 WITHSCORES