Java多线程与并发面试问答
1、什么是线程?什么是进程?他们之间有什么区别?
- 进程:操作系统资源分配的基本单位,有独立内存空间(如一个运行的Java程序)。
- 线程:CPU调度的基本单位,共享进程内存(如一个进程中的多个任务)。
维度 | 进程 | 线程 |
资源占用 | 高(独立内存) | 低(共享进程内存) |
切换开销 | 大(需切换内存空间) | 小 |
通信 | 复杂(IPC:管道、Socket) | 简单(直接共享内存) |
安全性 | 高(相互隔离) | 低(共享数据需同步) |
2、什么是并发和并行?
- 并发:单核处理多个任务(快速切换,看似同时执行)。
- 示例:单核CPU边下载文件边听音乐
- 并行:多核同时处理多个任务(真正同时执行)。
- 示例:8核CPU同时渲染视频的8个片段。
3、什么是线程安全?
- 定义:多线程访问共享资源时,程序仍能保持正确行为(数据一致、无脏读)。
- 核心问题:原子性、可见性、有序性。
- 解决方案:synchronized、volatile、锁、原子类(如AtomicInteger)。
4、如何创建线程?
// 方式1:继承Thread类 class MyThread extends Thread { @Override public void run() { System.out.println("Thread running"); } } new MyThread().start(); // 方式2:实现Runnable接口(避免单继承限制,更灵活) Runnable task = () -> System.out.println("Task running"); new Thread(task).start();
5、什么是ThreadLocal?它的应用场景是什么?
- 作用:为每个线程提供独立的变量副本(线程隔离)。
- 场景:
- 数据库连接(Connection)避免多线程共享。
- 用户会话(Session)管理(如Spring的RequestContextHolder)。
ThreadLocal<String> local = new ThreadLocal<>(); local.set("ThreadA"); // 线程A存数据 String value = local.get(); // 线程A取自己的数据
6、synchronized关键字的作用是什么?
synchronized 是 Java 中用于实现同步的关键字,它的核心作用是确保多个线程在访问共享资源时,同一时刻只有一个线程可以执行某个代码段或方法,从而避免出现数据不一致等线程安全问题。
- 用法:
//1.同步实例方法 //锁是当前对象实例(this) public class Counter { private int count = 0; // 锁是当前Counter对象实例 public synchronized void increment() { count++; // 这个操作是原子性的 } } //当多个线程操作同一个Counter对象的increment方法时,它们会互斥执行 //2.同步静态方法 //锁是当前类的 Class 对象(例如 Counter.class) public class Counter { private static int count = 0; // 锁是Counter.class这个类对象 public static synchronized void increment() { count++; } } //因为静态方法属于类而不属于实例,所以它用于控制对所有实例共享的静态资源的访问。 //3. 同步代码块 public class Counter { private int count = 0; // 自定义一个锁对象 private final Object lock = new Object(); public void increment() { // 同步代码块,锁是lock对象 synchronized (lock) { count++; } // 其他可以并发执行的代码... } public void doSomething() { // 同步代码块,锁是当前实例(this) synchronized (this) { // ... } } } //使用同步代码块的好处是可以减少锁的持有时间,提高并发性能,因为只有需要同步的代码被锁住,其他代码可以并发执行。
7、什么是ReentrantLock,它与 synchronized的区别是什么?
ReentrantLock 是 Java 并发包 java.util.concurrent.locks 下的一个类,它实现了 Lock 接口。顾名思义,它是一个可重入的互斥锁,其基本行为和作用与 synchronized 关键字类似,但提供了更强大、更灵活的功能。
特性 | synchronized (隐式锁) | ReentrantLock(显式锁) |
实现机制 | JVM 级别实现,内置关键字 | JDK 级别实现,是一个类 |
锁的获取与释放 | 自动管理。进入同步块获取,退出(或异常)时释放。 | 手动控制。必须显式调用 lock() 和 unlock()方法,通常放在 try-finally块中确保释放。 |
可中断性 | 不支持。如果一个线程在等待锁,它会一直阻塞,无法被中断。 | 支持。可以使用 lockInterruptibly()方法,让等待锁的线程响应中断。 |
尝试非阻塞获取锁 | 不支持。 | 支持。可以使用 tryLock()方法尝试获取锁,如果获取失败立即返回,不会阻塞。 |
超时获取锁 | 不支持。 | 支持。可以使用 tryLock(long time, TimeUnit unit)方法,在指定时间内尝试获取锁,超时则失败。 |
公平锁 | 只支持非公平锁(抢占式,不保证先来先得)。 | 支持公平锁和非公平锁(通过构造函数 new ReentrantLock(true)创建公平锁)。公平锁能减少线程饥饿,但性能开销较大。 |
条件变量 (Condition) | 只有一个等待队列,通过 wait(), notify(), notifyAll()进行通信。 | 可以创建多个 Condition 对象,实现更精确的线程等待/唤醒控制。 |
8、volatile关键字的作用?
- 作用:
- 可见性:写操作立即刷新到主内存,读操作从主内存读取。
- 禁止指令重排序(如单例模式的双重检查锁)。
- 局限:不保证原子性(如i++仍需synchronized)。
9、什么是死锁,如何避免死锁?
- 死锁条件(缺一不可):
- 互斥访问
- 持有并等待
- 不可剥夺
- 循环等待
- 避免方法:
- 顺序加锁:所有线程按固定顺序获取锁。
- 超时释放:用tryLock(timeout)打破等待。
- 银行家算法:系统预分配资源时检查安全性。
10、Thread.sleep和Object.wait()有什么区别?
区别 | sleep() | wait() |
所属类 | Thread静态方法 | Object的实例方法 |
锁释放 | 不释放锁 | 释放锁 |
唤醒条件 | 时间结束后自动唤醒 | 需notify()/notifyAll() |
使用场景 | 暂停执行 | 线程间通信 |
11、什么是线程池?为什么使用线程池?
- 定义:管理一组线程的容器(复用线程,避免频繁创建销毁)。
- 优点:
- 降低资源消耗(线程复用)
- 提高响应速度(任务直接执行)
- 控制并发度(避免系统崩溃)
- 统一管理任务(队列、拒绝策略)
ExecutorService pool = Executors.newFixedThreadPool(5); pool.submit(() -> System.out.println("Task executed"));
12、Java并发集合类有哪些?
- ConcurrentHashMap:分段锁/CAS实现高并发Map。
- CopyOnWriteArrayList:读无锁,写时复制(适合读多写少)。
- BlockingQueue:阻塞队列(生产者-消费者模型)。
- 实现类:ArrayBlockingQueue、LinkedBlockingQueue。
- ConcurrentLinkedQueue:非阻塞高性能队列(CAS)。
- ConcurrentSkipListMap:跳表实现的并发有序Map。
整理面试过程中的测试问答,常看常新,多多学习!有些问题是从其他人那里转载而来,会在文章下面注明出处,希望大家多多支持~~,觉得满意的话就送一朵小花花,谢谢! 内容目录:https://www.nowcoder.com/discuss/779856598809264128?sourceSSR=users