互联网大厂Java面试大揭秘:核心知识与实战问答
面试官:欢迎你来面试,咱们开始第一轮提问。首先,说说Java中的多线程有哪些实现方式?
王铁牛:嗯,有继承Thread类和实现Runnable接口这两种方式。
面试官:回答得不错。那再问个问题,线程池的核心参数都有哪些,分别有什么作用?
王铁牛:线程池的核心参数有corePoolSize、maximumPoolSize、keepAliveTime、unit和workQueue。corePoolSize是线程池的核心线程数,当提交的任务数小于corePoolSize时,会创建新线程执行任务;maximumPoolSize是线程池允许的最大线程数;keepAliveTime是线程池中非核心线程的存活时间;unit是keepAliveTime的时间单位;workQueue是任务队列,当提交的任务数大于corePoolSize时,会将任务放入workQueue中。
面试官:很好,看来你对线程池有一定的了解。最后一个问题,在多线程环境下,如何保证数据的一致性?
王铁牛:可以使用synchronized关键字或者Lock接口来实现同步,还可以使用volatile关键字保证变量的可见性。
面试官:好,第一轮面试结束。接下来进行第二轮提问。说说JVM的内存结构都有哪些?
王铁牛:JVM的内存结构包括堆、栈、方法区、程序计数器、本地方法栈。
面试官:那堆内存又分为哪几个区域?
王铁牛:堆内存分为新生代、老年代和永久代。
面试官:永久代现在在JDK 1.8中叫什么?
王铁牛:呃……叫……叫元空间吧。
面试官:嗯,勉强算你答对了。第二轮面试也结束了,现在进入第三轮提问。说说HashMap的底层实现原理。
王铁牛:HashMap底层是数组+链表+红黑树的结构。当链表长度大于8且数组长度大于64时,链表会转换为红黑树。
面试官:那HashMap在多线程环境下会有什么问题?
王铁牛:会出现数据丢失和死循环的问题。
面试官:如何解决这些问题?
王铁牛:可以使用ConcurrentHashMap来替代HashMap。
面试官:好,三轮面试都结束了。回去等通知吧。
答案解析
多线程实现方式
- 继承Thread类:通过继承Thread类并重写run方法来创建线程。每个Thread类的实例代表一个线程,在run方法中定义线程要执行的任务。
- 实现Runnable接口:实现Runnable接口的类需要实现run方法,然后将该类的实例作为参数传递给Thread类的构造函数来创建线程。这种方式使得一个类可以同时被多个线程共享执行任务。
线程池核心参数及作用
- corePoolSize:线程池的核心线程数。当提交的任务数小于corePoolSize时,线程池会创建新线程来执行任务。
- maximumPoolSize:线程池允许的最大线程数。当提交的任务数大于corePoolSize且任务队列已满时,线程池会创建新线程来执行任务,直到线程数达到maximumPoolSize。
- keepAliveTime:线程池中非核心线程的存活时间。当线程池中的线程数大于corePoolSize时,多余的线程(非核心线程)在空闲一段时间后会被销毁,空闲时间由keepAliveTime和unit指定。
- unit:keepAliveTime的时间单位。
- workQueue:任务队列。当提交的任务数大于corePoolSize时,会将任务放入workQueue中。常见的任务队列有ArrayBlockingQueue、LinkedBlockingQueue等。
多线程环境下保证数据一致性的方法
- synchronized关键字:用于修饰代码块或方法,保证同一时刻只有一个线程能访问被修饰的代码块或方法,从而实现同步。
- Lock接口:提供了比synchronized更灵活的锁控制,如可中断锁、公平锁等。通过Lock接口的lock和unlock方法来实现同步。
- volatile关键字:保证变量的可见性,即当一个变量被声明为volatile时,它会保证对该变量的写操作会立即刷新到主内存中,而读操作会从主内存中读取最新的值,避免了线程缓存导致的数据不一致问题。
JVM内存结构
- 堆:是JVM中最大的一块内存区域,用于存储对象实例。堆又分为新生代、老年代和永久代(JDK 1.8中为元空间)。
- 栈:每个线程都有自己独立的栈空间,用于存储局部变量、方法调用等信息。
- 方法区:用于存储类信息、常量、静态变量等。
- 程序计数器:记录当前线程执行的字节码指令地址。
- 本地方法栈:用于执行本地方法(用C或C++实现的方法)。
HashMap底层实现原理
HashMap底层是数组+链表+红黑树的结构。
- 数组:HashMap使用一个Entry数组来存储键值对,数组的每个元素是一个Entry对象,Entry对象包含键、值、指向下一个Entry对象的引用。
- 链表:当两个或多个键值对的哈希值相同(哈希冲突)时,会将这些键值对存储在同一个链表中。
- 红黑树:当链表长度大于8且数组长度大于64时,链表会转换为红黑树,以提高查询效率。
HashMap在多线程环境下的问题及解决方法
- 数据丢失问题:在扩容时可能会导致数据丢失。当HashMap进行扩容时,会重新计算每个键值对的哈希值并重新插入到新的数组中,如果在扩容过程中多个线程同时操作,可能会导致部分数据丢失。
- 死循环问题:在扩容时可能会导致死循环。由于HashMap在扩容时会将链表中的元素重新插入到新的数组中,如果在这个过程中多个线程同时操作,可能会导致链表形成环形结构,从而导致死循环。
- 解决方法:可以使用ConcurrentHashMap来替代HashMap。ConcurrentHashMap采用了分段锁的机制,允许多个线程同时访问不同的段,从而提高了并发性能,并且避免了HashMap在多线程环境下的问题。