模拟面试总结

#VibeCoding·九月创作之星挑战赛#
1.自我介绍
2.Java多线程实现方式以及区别

        实现Runnable接口,继承Thread类,实现Callable接口

//继承Thread类
class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("Thread running:"+Thread.currentThread().getname());
    }
}
//使用方式
MyThread thread=new MyThread();
thread.start();
/*
    直接继承Thread类,重写run()方法
    使用简单直观
    每个线程都是独立的Thread对象
*/
//实现Runnable接口
class MyRunnable implements Runnable{
    @Override
    public void run(){
        System.out.println("Runnable running:"+Thread.currentThread().getName());
    }
}
//使用方式
Thread thread=new Thread(new MyRunnable());
thread.start();
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String>{
    @Override
    public String call() throws Exception{
        return "Callable result:"+Thread.currentThread().getName();
    }
}
// 使用方式
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get(); // 获取返回值
特性Thread类Runnable接口Callable接口
返回值无返回值无返回值有返回值(Future)
异常处理不能抛出受检异常不能抛出受检异常可以抛出异常
继承关系单继承限制可多实现接口可多实现接口
资源共享每个线程独立对象可共享同一个Runnable实例可共享同一个Callable实例
使用场景简单线程任务推荐使用,更灵活需要返回结果的异步任务
  1. 推荐使用Runnable接口:避免单继承限制,更符合面向接口编程原则
  2. 资源共享优势:多个线程可以共享同一个Runnable实例,节省资源
  3. Callable的特殊性:需要返回值或异常处理时使用,配合线程池更高效
  4. 实际开发建议:通常使用线程池+Callable/Runnable,而不是直接new Thread()

最佳实践:优先选择实现Runnable接口,需要返回值时使用Callable接口,尽量避免直接继承Thread类。

面试官问:请说一下实现多线程的三种方法及其区别?

可以这样回答:

"Java中实现多线程主要有三种方式:

第一种是继承Thread类,通过重写run()方法来实现线程逻辑。这种方式简单直接,但由于Java是单继承,会占用继承名额,不够灵活。

第二种是实现Runnable接口,这也是最推荐的方式。它避免了单继承的限制,多个线程可以共享同一个Runnable实例,更符合面向接口编程的原则,资源利用率更高。

第三种是实现Callable接口,它与Runnable的主要区别在于:Callable可以有返回值,通过Future对象获取执行结果;还可以抛出受检异常,更适合需要处理返回值和异常的场景。

在实际开发中,我们通常优先选择实现Runnable接口,需要返回值时使用Callable接口,配合线程池来管理线程,而不是直接new Thread()来创建线程,这样可以更好地控制线程资源和提高性能。"

3.说一下HashMap底层实现

1.8版本之后核心数据结构是数组+链表/红黑树

  • 数组:称为哈希桶(Hash Bucket),存储链表或红黑树的头节点
  • 链表:解决哈希冲突,当链表长度>=8时转换为红黑树
  • 红黑树:提高查询效率,当节点数<6时退化为链表

为什么使用数组:1.快速随机访问  2.内存连续,访问效率高

链表在HashMap中的作用:1.解决哈希冲突  2.链式存储,动态扩容

为什么需要红黑树(自平衡的二叉查找树,能够保证最坏情况下操作时间复杂度为O(log n)):1.链表过长时查询效率低(O(n))2.红黑树保证查询效率(O(log n))

关键参数:

初始容量:默认16,必须是2的幂次

负载因子:默认0.75,控制扩容时机

扩容阈值:容量 x 负载因子,达到时触发扩容

工作原理

1.哈希计算

// 计算key的哈希值,让高位也参与运算,减少哈希冲突
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

2.确定数组下标

// 通过 (n-1) & hash 确定在数组中的位置
int index = (table.length - 1) & hash;

3.插入逻辑

  • 如果该位置为空,直接插入新节点
  • 如果已有节点,遍历链表/红黑树:
    • 找到相同key:更新value
    • 未找到相同key:添加到链表末尾或红黑树

4.扩容机制(resize)

  • 创建新数组(原容量×2)
  • 重新计算所有节点的位置
  • 节点在新数组中的位置:要么在原位置,要么在原位置+原容量
线程安全问题

HashMap是非线程安全的,多线程环境下可能产生:

  • 死循环(JDK1.7及之前)
  • 数据丢失
  • 使用ConcurrentHashMap或Collections.synchronizedMap保证线程安全
面试官问:请说一下HashMap的底层实现原理?

可以这样回答:

"HashMap在JDK1.8中采用数组+链表+红黑树的结构。数组称为哈希桶,每个桶可以存储链表或红黑树。

当插入元素时,首先通过key的hashCode计算哈希值,再通过(n-1)&hash确定在数组中的位置。如果该位置为空,直接插入;如果已有元素,则遍历链表比较key,相同则更新value,不同则添加到链表末尾。

当链表长度达到8且数组长度≥64时,链表会转换为红黑树以提高查询效率;当节点数减少到6时,红黑树会退化为链表。

HashMap有负载因子(默认0.75)和扩容机制。当元素数量达到容量×负载因子时,会进行扩容,新容量为原容量的2倍,并重新计算所有元素的位置。

需要注意的是,HashMap是非线程安全的,多线程环境下需要使用ConcurrentHashMap或同步包装类来保证线程安全。"

4.数组与链表的区别

面试官问:请说一下数组和链表的区别?

可以这样回答:

"数组和链表是两种基本的数据结构,主要区别在于:

  1. 内存存储:数组需要连续的内存空间,而链表的节点可以分散存储
  2. 访问效率:数组支持随机访问,时间复杂度O(1);链表需要顺序访问,时间复杂度O(n)
  3. 插入删除:数组插入删除需要移动元素,时间复杂度O(n);链表只需要修改指针,时间复杂度O(1)
  4. 内存管理:数组大小固定,可能浪费空间;链表动态分配,更灵活

选择建议

  • 如果需要频繁随机访问,选择数组
  • 如果需要频繁插入删除,选择链表
  • 在实际开发中,ArrayList基于数组,LinkedList基于链表"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值