在互联网大厂的面试室里,面试官严肃地坐在对面,看着眼前的求职者王铁牛。
第一轮: 面试官:请你简述一下 Java 的核心知识有哪些? 王铁牛:Java 的核心知识包括面向对象编程的三大特性:封装、继承、多态,还有基本数据类型、引用数据类型等。 面试官:不错,那你说说 Java 中的访问修饰符有哪些? 王铁牛:有 public、private、protected 和默认(没有修饰符),它们分别控制着类、方法和变量的访问范围。 面试官:很好,那你再讲讲 Java 中的自动装箱和拆箱是怎么回事? 王铁牛:自动装箱就是把基本数据类型自动转换成对应的包装类对象,拆箱则是相反,把包装类对象自动转换成基本数据类型。
第二轮: 面试官:说说 JUC 中的常用类有哪些? 王铁牛:有 CountDownLatch、CyclicBarrier、Semaphore 等。 面试官:那你讲讲 CountDownLatch 的作用和使用场景吧? 王铁牛:它主要用于线程间的同步,比如在一个线程完成某些操作后,通知其他线程继续执行。比如在一个主线程启动多个子线程,主线程需要等待所有子线程都执行完毕后再继续执行,就可以使用 CountDownLatch。 面试官:那 CyclicBarrier 又有什么特点呢? 王铁牛:CyclicBarrier 可以让一组线程到达一个屏障(barrier)时被阻塞,直到所有线程都到达屏障时才会继续执行。它可以重复使用,而 CountDownLatch 只能使用一次。
第三轮: 面试官:谈谈 JVM 的内存结构吧? 王铁牛:JVM 的内存结构主要分为堆、栈、方法区等。堆用于存储对象实例,栈用于存储局部变量、方法参数等,方法区用于存储类信息、常量等。 面试官:那你说说堆分为哪几种类型? 王铁牛:有新生代和老年代,新生代又分为 Eden 区和两个 Survivor 区。 面试官:最后,说说多线程在 JVM 中的实现机制吧? 王铁牛:(挠挠头)这个……不太清楚了。
面试官:没关系,今天的面试就到这里吧,你可以回家等通知。
答案:
- Java 的核心知识:
- 面向对象编程的三大特性:封装是将数据和操作封装在类中,对外隐藏内部实现细节;继承是子类继承父类的属性和方法;多态是同一操作作用于不同的对象可以有不同的表现形式。基本数据类型如 int、double 等,引用数据类型如类、接口、数组等。
- 访问修饰符:public 修饰的成员可以被任何类访问;private 修饰的成员只能在本类中访问;protected 修饰的成员可以被本类和子类访问,在同一个包中的其他类也可以访问;默认修饰符(没有修饰符)的成员只能在本包中的类访问。
- 自动装箱和拆箱:自动装箱是 Java 5 引入的特性,例如将 int 类型自动转换成 Integer 包装类对象,拆箱则是将 Integer 包装类对象自动转换成 int 类型。
- JUC 中的常用类:
- CountDownLatch:用于线程间的同步,通过一个计数器来控制线程的等待和唤醒。例如在一个主线程启动多个子线程,主线程需要等待所有子线程都执行完毕后再继续执行,就可以使用 CountDownLatch。初始化时传入计数器的初始值,每个子线程执行完毕后调用 countDown 方法减 1,当计数器的值为 0 时,等待的线程会被唤醒。
- CyclicBarrier:可以让一组线程到达一个屏障时被阻塞,直到所有线程都到达屏障时才会继续执行。它可以重复使用,初始化时传入参与线程的数量,当所有线程都调用 await 方法时,线程会被阻塞,直到所有线程都到达屏障。当所有线程都通过屏障后,屏障会被重置,可以再次使用。
- Semaphore:用于控制同时访问某个资源的线程数量。初始化时传入许可证的数量,每个线程在访问资源前需要获取许可证,获取成功后可以访问资源,访问完毕后释放许可证。当许可证数量为 0 时,线程会被阻塞,直到有许可证可用。
- JVM 的内存结构:
- 堆:是 JVM 管理的最大的一块内存区域,用于存储对象实例和数组。堆分为新生代和老年代,新生代又分为 Eden 区和两个 Survivor 区。Eden 区用于存放新创建的对象,Survivor 区用于存放经过一次垃圾回收后仍然存活的对象。老年代用于存放经过多次垃圾回收后仍然存活的对象。
- 栈:用于存储局部变量、方法参数、返回值等。每个线程都有自己的栈,栈的大小是固定的,随着方法的调用和返回,栈帧会在栈中入栈和出栈。
- 方法区:用于存储类信息、常量、静态变量、即时编译器编译后的代码等。方法区是共享的内存区域,所有线程都可以访问。
- 多线程在 JVM 中的实现机制:
- 在 JVM 中,每个线程都有自己的栈空间,用于存储线程的局部变量、方法参数等。当线程启动时,JVM 会为其创建一个栈,并将线程的起始方法压入栈中。
- 线程的执行是通过线程调度器来控制的,调度器会根据优先级和时间片等因素来选择要执行的线程。
- 多线程之间的通信可以通过共享内存或消息传递等方式来实现。共享内存是通过共享变量来实现的,多个线程可以同时访问和修改共享变量;消息传递是通过队列等数据结构来实现的,线程之间通过发送和接收消息来进行通信。
- 在 Java 中,多线程的实现方式主要有继承 Thread 类和实现 Runnable 接口两种。继承 Thread 类方式简单,但不适合多个线程共享资源的情况;实现 Runnable 接口方式更加灵活,可以避免单继承的限制,多个线程可以共享同一个 Runnable 对象。