Java 多线程与并发:春招面试核心知识

在当今多核处理器盛行的时代,多线程编程已成为 Java 开发中不可或缺的技能。它能充分利用硬件资源,提升程序的执行效率和响应速度。对于春招面试而言,多线程与并发知识是重点考察的领域,深入理解这些知识不仅有助于应对面试,更能为实际开发打下坚实的基础。

一、线程生命周期

线程的生命周期包括新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五个状态。

  • 新建状态:当使用new关键字创建一个线程对象时,线程处于新建状态,此时线程还未开始执行。例如Thread thread = new Thread(() -> System.out.println("Hello, Thread!"));。
  • 就绪状态:调用start()方法后,线程进入就绪状态,此时线程已经具备了运行的条件,但还没有被分配到 CPU 时间片,需要等待 CPU 调度。
  • 运行状态:当线程获得 CPU 时间片后,进入运行状态,开始执行run()方法中的代码。
  • 阻塞状态:在运行过程中,线程可能会因为某些原因进入阻塞状态,如调用wait()方法、sleep()方法,或者等待获取锁等。处于阻塞状态的线程不会被 CPU 调度,直到满足特定条件后才会重新进入就绪状态。
  • 死亡状态:当线程的run()方法执行完毕,或者因异常退出,线程进入死亡状态,此时线程生命周期结束。

面试题 1:线程在什么情况下会进入阻塞状态?

答案:线程进入阻塞状态主要有以下几种情况:

  • 调用Thread.sleep(long millis)方法,使线程睡眠指定的时间,在睡眠期间线程进入阻塞状态。
  • 调用对象的wait()方法,线程会释放持有的锁,并进入该对象的等待队列,等待其他线程调用notify()或notifyAll()方法唤醒。
  • 线程在获取锁时,如果锁被其他线程占用,线程会进入阻塞状态,等待获取锁。
  • 调用join()方法,当前线程会等待被调用join()方法的线程执行完毕,在此期间当前线程进入阻塞状态。

二、同步机制

在多线程环境下,为了避免线程安全问题,需要使用同步机制来保证共享资源的正确访问。Java 提供了多种同步机制,如synchronized关键字和Lock接口。

  • synchronized 关键字:可以修饰方法或代码块。修饰方法时,该方法成为同步方法,同一时刻只能有一个线程执行该方法;修饰代码块时,只有获取到该代码块关联的对象锁的线程才能执行代码块中的内容。例如:
java 复制代码
public class SynchronizedExample {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public void incrementBlock() {
        synchronized (this) {
            count++;
        }
    }
}
  • Lock 接口:java.util.concurrent.locks包下的Lock接口提供了更灵活的锁机制,如可中断的锁获取、公平锁与非公平锁等。常用的实现类有ReentrantLock。例如:
java 复制代码
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private int count = 0;
    private ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

面试题 2:synchronized 关键字和 Lock 接口有什么区别?

答案

  • 语法结构:synchronized是 Java 关键字,在编译时由编译器处理;Lock是一个接口,通过调用接口方法来实现锁的功能。
  • 锁获取方式:synchronized获取锁是隐式的,在进入同步代码块或方法时自动获取,退出时自动释放;Lock需要手动调用lock()方法获取锁,调用unlock()方法释放锁,通常放在finally块中以确保锁一定会被释放。
  • 锁的特性:synchronized提供的是不可中断的锁获取方式,一旦获取不到锁,线程会一直等待;Lock可以通过tryLock()方法尝试获取锁,获取不到时可以返回false,还可以通过lockInterruptibly()方法获取可中断的锁,在等待锁的过程中可以响应中断。
  • 锁的类型:synchronized只有一种非公平锁;Lock可以通过构造函数选择公平锁或非公平锁,ReentrantLock默认是非公平锁,创建时传入true可变为公平锁。

三、并发包

Java 的java.util.concurrent包(简称并发包)提供了丰富的并发工具类,如线程池、并发集合、同步器等。

  • 线程池:通过线程池可以复用线程,减少线程创建和销毁的开销,提高系统性能。常用的线程池有ThreadPoolExecutor,可以通过Executors工具类创建不同类型的线程池,如FixedThreadPool(固定大小线程池)、CachedThreadPool(缓存线程池)、SingleThreadExecutor(单线程线程池)等。例如:
java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            executorService.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " is running");
            });
        }
        executorService.shutdown();
    }
}
  • 并发集合:并发包提供了多种线程安全的集合类,如ConcurrentHashMap(前面已介绍)、ConcurrentLinkedQueue(基于链表的无界线程安全队列)、CopyOnWriteArrayList(写时复制的线程安全ArrayList)等。这些集合类在多线程环境下具有更好的性能和线程安全性。
  • 同步器:CountDownLatch、CyclicBarrier和Semaphore是常用的同步器。CountDownLatch用于让一个或多个线程等待其他线程完成一组操作后再继续执行;CyclicBarrier用于让一组线程相互等待,直到所有线程都到达某个屏障点后再继续执行;Semaphore用于控制同时访问某个资源的线程数量。例如:
java 复制代码
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        new Thread(() -> {
            try {
                Thread.sleep(1000);
                System.out.println("Thread 1 is done");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(2000);
                System.out.println("Thread 2 is done");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(3000);
                System.out.println("Thread 3 is done");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }).start();
        latch.await();
        System.out.println("All threads are done, main thread can continue");
    }
}

面试题 3:线程池的原理和优势是什么?

答案

  • 原理:线程池内部维护了一个线程队列和一个任务队列。当提交任务时,线程池会从线程队列中获取空闲线程来执行任务,如果线程队列中没有空闲线程,且任务队列未满,则将任务加入任务队列等待执行;如果任务队列也满了,根据线程池的拒绝策略处理新提交的任务。
  • 优势
    • 减少线程创建和销毁开销:线程的创建和销毁需要消耗系统资源,线程池可以复用已创建的线程,避免频繁创建和销毁线程带来的开销。
    • 控制并发线程数量:通过设置线程池的最大线程数和核心线程数,可以控制并发执行的线程数量,防止因线程过多导致系统资源耗尽。
    • 提高系统响应速度:由于线程池中有空闲线程可以立即执行任务,相比每次创建新线程执行任务,能更快地响应任务请求。

掌握 Java 多线程与并发知识,是在春招面试中脱颖而出的关键。下一篇,我们将探索 Java IO 与 NIO 的奥秘,继续为你的面试之路助力。

相关推荐
liuyunshengsir10 分钟前
Spring Boot 使用 Micrometer 集成 Prometheus 监控 Java 应用性能
java·spring boot·prometheus
路上阡陌19 分钟前
Java学习笔记(二十四)
java·笔记·学习
何中应28 分钟前
Spring Boot中选择性加载Bean的几种方式
java·spring boot·后端
苏苏大大30 分钟前
zookeeper
java·分布式·zookeeper·云原生
wclass-zhengge1 小时前
03垃圾回收篇(D3_垃圾收集器的选择及相关参数)
java·jvm
涛ing1 小时前
23. C语言 文件操作详解
java·linux·c语言·开发语言·c++·vscode·vim
5xidixi1 小时前
Java TCP协议(2)
java·tcp/ip
2013crazy1 小时前
Java 基于 SpringBoot+Vue 的校园兼职平台(附源码、部署、文档)
java·vue.js·spring boot·兼职平台·校园兼职·兼职发布平台
小高不明1 小时前
仿 RabbitMQ 的消息队列3(实战项目)
java·开发语言·spring·rabbitmq·mybatis
兩尛1 小时前
订单状态定时处理、来单提醒和客户催单(day10)
java·前端·数据库