多线程面试题
1. 基础概念
- 什么是线程?
- 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
- 线程和进程的区别?
- 进程是资源分配的基本单位,线程是CPU调度的基本单位。
- 进程有独立的地址空间,线程共享进程的地址空间。
- 进程切换开销大,线程切换开销小。
- 什么是多线程?
- 多线程是指在一个进程中同时运行多个线程,每个线程执行不同的任务。
- 多线程的优点和缺点?
- 优点:提高CPU利用率,提高程序响应速度,简化程序结构。
- 缺点:线程安全问题,调试困难,上下文切换开销。
2. 线程的创建和启动
- Java中创建线程的方式有哪些?
- 继承
Thread
类。 - 实现
Runnable
接口。 - 实现
Callable
接口,结合FutureTask
使用。 - 使用线程池。
- 继承
Runnable
和Callable
的区别?Runnable
没有返回值,Callable
有返回值。Runnable
不能抛出受检异常,Callable
可以。
start()
和run()
方法的区别?start()
方法会启动一个新线程,并调用run()
方法。run()
方法只是普通的方法调用,不会启动新线程。
3. 线程的生命周期
- 线程的生命周期有哪些状态?
- 新建(New)
- 就绪(Runnable)
- 运行(Running)
- 阻塞(Blocked)
- 等待(Waiting)
- 超时等待(Timed Waiting)
- 终止(Terminated)
- 如何让线程进入等待状态?
- 使用
Object.wait()
方法。 - 使用
Thread.sleep()
方法。 - 使用
LockSupport.park()
方法。
- 使用
4. 线程同步与锁
- 什么是线程安全?
- 线程安全是指多个线程访问共享资源时,不会出现数据不一致或不可预期的结果。
- 如何保证线程安全?
- 使用
synchronized
关键字。 - 使用
ReentrantLock
。 - 使用
volatile
关键字。 - 使用原子类(如
AtomicInteger
)。
- 使用
synchronized
和ReentrantLock
的区别?synchronized
是关键字,ReentrantLock
是类。ReentrantLock
支持公平锁和非公平锁,synchronized
只支持非公平锁。ReentrantLock
可以尝试获取锁、可中断、可超时,synchronized
不行。
- 什么是死锁?如何避免死锁?
- 死锁是指多个线程互相持有对方所需的资源,导致所有线程都无法继续执行。
- 避免死锁的方法:避免嵌套锁、按顺序获取锁、使用超时机制。
volatile
关键字的作用?volatile
保证变量的可见性,但不保证原子性。- 禁止指令重排序。
5. 线程池
- 什么是线程池?
- 线程池是一种管理线程的机制,通过复用线程来减少线程创建和销毁的开销。
- Java中如何创建线程池?
- 使用
Executors
工厂类创建线程池。 - 使用
ThreadPoolExecutor
自定义线程池。
- 使用
- 线程池的核心参数有哪些?
- 核心线程数(corePoolSize)
- 最大线程数(maximumPoolSize)
- 空闲线程存活时间(keepAliveTime)
- 任务队列(workQueue)
- 线程工厂(threadFactory)
- 拒绝策略(RejectedExecutionHandler)
- 线程池的拒绝策略有哪些?
AbortPolicy
:直接抛出异常。CallerRunsPolicy
:由调用线程执行任务。DiscardOldestPolicy
:丢弃队列中最旧的任务。DiscardPolicy
:直接丢弃任务。
6. 并发工具类
CountDownLatch
的作用?CountDownLatch
允许一个或多个线程等待其他线程完成操作。
CyclicBarrier
的作用?CyclicBarrier
让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,所有线程才会继续执行。
Semaphore
的作用?Semaphore
用于控制同时访问特定资源的线程数量。
Exchanger
的作用?Exchanger
用于两个线程之间交换数据。
7. 线程之间如何通信
1. 共享变量
- 问题:需要解决线程安全问题(如数据竞争)。
- 解决方案:
- 使用
synchronized
关键字。 - 使用
volatile
关键字(保证可见性)。使变量在多个线程间可见,但无法保证原子性, - 使用原子类(如
AtomicInteger
)。
- 使用
2. wait() 和 notify() / notifyAll()
wait()
:使当前线程进入等待状态,并释放锁。notify()
:唤醒一个等待的线程。notifyAll()
:唤醒所有等待的线程。- 注意:必须在
synchronized
块中使用。
3. BlockingQueue
BlockingQueue
是一个线程安全的队列,支持阻塞操作。- 当队列为空时,消费者线程会被阻塞;当队列满时,生产者线程会被阻塞。
- 常见的实现类:
ArrayBlockingQueue
、LinkedBlockingQueue
。
4. Exchanger
Exchanger
用于两个线程之间交换数据。当一个线程调用
exchange()
方法时,它会等待另一个线程也调用exchange()
方法,然后交换数据。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这个两个线程通过exchange方法交换数据,如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。因此使用Exchanger的中断时成对的线程使用exchange()方法,当有一对线程到达了同步点,就会进行交换数据,因此该工具类的线程对象是成对的。
import java.util.concurrent.Exchanger; public class ExchangerDemo { public static void main(String[] args) { Exchanger<String> exchanger = new Exchanger<>(); new Thread(() -> { try { String data = "Data from Thread 1"; System.out.println("Thread 1 sending: " + data); String received = exchanger.exchange(data); System.out.println("Thread 1 received: " + received); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); new Thread(() -> { try { String data = "Data from Thread 2"; System.out.println("Thread 2 sending: " + data); String received = exchanger.exchange(data); System.out.println("Thread 2 received: " + received); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); } } // 执行打印 Thread 1 sending: Data from Thread 1 Thread 1 received: Data from Thread 2 Thread 2 sending: Data from Thread 2 Thread 2 received: Data from Thread 1
5. Future
和 Callable
Callable
用于返回结果的线程任务。Future
用于获取异步任务的结果。import java.util.concurrent.*; public class FutureDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.submit(() -> { Thread.sleep(1000); return "Task completed"; }); System.out.println("Waiting for result..."); String result = future.get(); // 阻塞等待结果 System.out.println("Result: " + result); executor.shutdown(); } }
6. ThreadLocal
ThreadLocal
为每个线程提供独立的变量副本,避免线程安全问题。适用于线程间数据隔离的场景。
public class ThreadLocalDemo { private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0); public static void main(String[] args) { new Thread(() -> { threadLocal.set(1); System.out.println("Thread 1: " + threadLocal.get()); }).start(); new Thread(() -> { threadLocal.set(2); System.out.println("Thread 2: " + threadLocal.get()); }).start(); } } //执行返回 Thread 1: 1 Thread 2: 2
7.使用Thread.join()
Thread.join()
是 Java 中用于线程通信的一种简单方式。它的作用是让当前线程等待调用 join()
的线程执行完毕后再继续执行。通过 join()
,可以实现线程之间的同步和通信。
public class JoinExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完毕");
});
Thread thread2 = new Thread(() -> {
System.out.println("线程2开始执行");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完毕");
});
thread1.start();
thread2.start();
try {
// 主线程等待 thread1 执行完毕
thread1.join();
System.out.println("主线程等待 thread1 执行完毕");
// 主线程等待 thread2 执行完毕
thread2.join();
System.out.println("主线程等待 thread2 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程继续执行");
}
}
//线程1开始执行
//线程2开始执行
//线程2执行完毕
//线程1执行完毕
//主线程等待 thread1 执行完毕
//主线程等待 thread2 执行完毕
//主线程继续执行
Thread.join()
的工作原理
- 当调用
thread.join()
时,当前线程(通常是主线程)会进入等待状态,直到thread
执行完毕。 - 如果
thread
已经执行完毕,join()
会立即返回。 - 如果
thread
还未启动,join()
会等待thread
启动并执行完毕。
Thread.join()
的超时机制
join()
方法还支持设置超时时间,如果在指定时间内目标线程未执行完毕,当前线程会继续执行。
总结
线程通信的方式有很多种,选择合适的方式取决于具体的应用场景:
- 如果需要简单的数据共享,可以使用共享变量和锁机制。
- 如果需要线程间的协调,可以使用
wait()
/notify()
或BlockingQueue
。 - 如果需要更高级的同步工具,可以使用
CountDownLatch
、CyclicBarrier
、Semaphore
、Exchanger
等。
8. 高级话题
- 什么是CAS操作?
- CAS(Compare And Swap)是一种无锁算法,通过比较并交换来实现线程安全。
ThreadLocal
的作用?ThreadLocal
为每个线程提供独立的变量副本,避免线程安全问题。
- 什么是AQS(AbstractQueuedSynchronizer)?
- AQS是Java并发包中用于构建锁和同步器的基础框架。
- 如何实现一个自定义的同步器?
- 继承
AbstractQueuedSynchronizer
并实现其模板方法。
- 继承
- 什么是Fork/Join框架?
- Fork/Join框架是Java 7引入的并行任务执行框架,适用于分治算法。
9. 实战问题
- 如何实现一个生产者-消费者模型?
- 使用
BlockingQueue
实现。 - 使用
wait()
和notify()
实现。
- 使用
- 如何实现一个线程安全的单例模式?
- 使用双重检查锁定(Double-Checked Locking)。
- 使用静态内部类实现。
- 如何实现一个线程安全的计数器?
- 使用
AtomicInteger
。 - 使用
synchronized
或ReentrantLock
。
- 使用
- 如何实现一个线程安全的LRU缓存?
- 使用
LinkedHashMap
并重写removeEldestEntry
方法。 - 使用
ConcurrentHashMap
和ReentrantLock
。
- 使用
- 如何实现一个线程池?
- 使用
ThreadPoolExecutor
自定义线程池。
- 使用
10. 性能调优
- 如何优化多线程程序的性能?
- 减少锁的粒度。
- 使用无锁数据结构。
- 合理设置线程池参数。
- 如何检测和解决线程死锁?
- 使用
jstack
工具检测死锁。 - 使用
ThreadMXBean
检测死锁。
- 使用
- 如何监控线程池的状态?
- 使用
ThreadPoolExecutor
提供的getPoolSize()
、getActiveCount()
等方法。
- 使用
11. 其他语言中的多线程
- Python中的多线程与多进程的区别?
- Python中的多线程受GIL(全局解释器锁)限制,适合I/O密集型任务。
- 多进程适合CPU密集型任务。
- Go语言中的Goroutine是什么?
- Goroutine是Go语言中的轻量级线程,由Go运行时管理。
12. 分布式系统中的多线程
- 分布式锁的实现方式有哪些?
- 基于数据库实现。
- 基于Redis实现。
- 基于Zookeeper实现。
- 如何保证分布式系统中的线程安全?
- 使用分布式锁。
- 使用消息队列。
13. 操作系统中的多线程
- 操作系统如何调度线程?
- 操作系统使用调度算法(如时间片轮转、优先级调度)来分配CPU时间。
- 什么是用户级线程和内核级线程?
- 用户级线程由用户空间的线程库管理,内核不可见。
- 内核级线程由操作系统内核管理。
14. 并发编程模型
- 什么是Actor模型?
- Actor模型是一种并发编程模型,每个Actor是一个独立的计算单元,通过消息传递进行通信。
- 什么是CSP模型?
- CSP(Communicating Sequential Processes)模型通过通道(Channel)进行线程间通信。
15. 其他
什么是线程上下文切换?
- 线程上下文切换是指CPU从一个线程切换到另一个线程时,保存当前线程的状态并加载下一个线程的状态。
如何减少线程上下文切换?
- 减少线程数。
- 使用无锁编程。
- 使用协程(Coroutine)。
什么是协程?
- 协程是一种轻量级的线程,由用户控制调度,避免了线程上下文切换的开销。
什么是线程饥饿?
- 线程饥饿是指某些线程长时间得不到CPU时间,导致任务无法执行。
如何避免线程饥饿?
- 使用公平锁。
- 合理设置线程优先级。
什么是线程优先级?
- 线程优先级是操作系统调度线程时的一个参考因素,优先级高的线程更容易获得CPU时间。
如何设置线程优先级?
- 使用
Thread.setPriority()
方法设置线程优先级。
- 使用
日夜颠倒头发少 ,单纯好骗恋爱脑 ,会背九九乘法表 ,下雨只会往家跑 ,搭讪只会说你好 ---- 2050781802@qq.com