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 的奥秘,继续为你的面试之路助力。

相关推荐
杉之2 分钟前
Java中的不可变集合
java·笔记·学习
潘多编程5 分钟前
Gradle实战指南:从入门到进阶,与Maven的深度对比
java·maven
故城、7 分钟前
MQ中的RabbitMQ
java·mq
橘猫云计算机设计23 分钟前
基于JavaWeb的二手图书交易系统(源码+lw+部署文档+讲解),源码可白嫖!
java·开发语言·前端·毕业设计·php
猿java24 分钟前
程序员,你使用过灰度发布吗?
java·分布式·后端
我的div丢了肿么办24 分钟前
vue3第二次传递数据方法无法获取到最新的值
前端·面试·github
兰亭序咖啡26 分钟前
学透Spring Boot — 007. 加载外部配置
android·java·spring boot
当归102427 分钟前
Tomcat中的webapps的访问方式和java -jar内置Tomcat的访问方式的区别
java·tomcat·jar
eason_fan33 分钟前
前端面试手撕代码(字节)
前端·算法·面试
magic 24536 分钟前
监听器(Listener)详解
java·servlet·tomcat