[Java-juc] 多线程
2026年3月21日大约 5 分钟
[Java-juc] 多线程
Java 的内存模型(JMM)介绍一下
总结
定义主内存与工作内存,保障可见性、原子性、有序性。
JMM 的本质就是屏蔽各种硬件和操作系统的内存访问差异,
定义主内存(大家共享的内存)和工作内存(每个线程自己的缓存)
并通过 volatile、synchronized 这些关键字,来确保多线程环境下的数据一致性(可见性、原子性、有序性)。
注意
volatile 只能保证可见性和有序性,不能保证原子性。
java多线程是什么?需要注意什么?
Java 多线程是指在一个 Java 程序中运行多个线程,这些线程共享程序的内存空间,
但有各自的栈和程序计数器,能同时执行不同的任务,提升程序效率。
需要注意什么:
- 线程安全问题:线程同时操作共享数据,导致数据不一致。
- 线程间通信问题:比如说一个线程生产数据,另一个线程消费数据,需要通过
wait()和notify()等机制进行协调。
以防数据出错或线程无限等待。
java里面的线程和操作系统的线程一样吗?
Java 底层会调用 pthread_create 来创建线程,所以是一样的。
使用多线程要注意哪些问题?
要注意线程安全问题:
- 原子性:操作不能被干扰。操作是一个整体,要么都执行,要么都不执行。可以使用atomic包下相关类或者synchronized来保证。
- 可见性:一个线程对共享变量修改,另一个线程能够立即看到。使用
volatile、synchronized等机制保证可见性。 - 有序性:通过java的 happens-before 规则来保证指令执行的顺序。
保证数据的一致性有哪些方案呢?
- 事务管理:数据库事务,确保一系列操作要么全部成功,要么全部失败。
- 锁机制:使用锁对共享资源进行互斥访问,如
synchronized、ReentrantLock等。 - 版本控制:通过乐观锁的版本号法,确保数据在更新时没有被其他线程修改。
线程的创建方式有哪些?
- 继承
Thread类:创建一个类继承Thread,并重写run()方法。 - 实现
Runnable接口:创建一个类实现Runnable接口,并实现run()方法,然后将该类的实例传递给Thread。可以用lambda表达式简化代码。 - 实现
Callable接口和FutureTask:创建一个类实现Callable接口,并实现call()方法,然后将该类的实例传递给FutureTask,再将FutureTask传递给Thread。 - 使用线程池:通过Executors工厂类创建线程池,如
Executors.newFixedThreadPool(),并提交任务执行。线程池可以提高线程复用性。
怎么启动线程 ?
使用start()方法启动线程,会创建新的线程来执行run()方法中的代码。
如何停止一个线程的运行?
- 通过打断标记:调用
interrupt()方法设置线程的打断标记,线程可以通过isInterrupted()方法检查是否被打断,并在适当的时候退出。 stop()暴力停止: 已废弃,可能会导致难以预料的行为。
调用 interrupt 是如何让线程抛出异常的?
- 当线程处于
wait()、sleep()、join()等阻塞状态时,调用interrupt()会抛出InterruptedException异常,并清除线程的打断标记。 - 如果是正常运行状态,调用
interrupt()会设置线程的打断标记,不会抛出异常。
线程的状态有哪些
在Java线程的生命周期中,线程可以处于以下6种状态:
- NEW:实例化后,未启动。
- RUNNABLE:调用start()后,含“就绪”和“运行中”。
- BLOCKED:等待获取synchronized锁。
- WAITING:无限期等待(如
wait(),join(),park())。 - TIMED_WAITING:限期等待(如
sleep(),wait(timeout))。 - TERMINATED:任务执行完毕或异常退出。
sleep 和 wait的区别是什么?
- 所属分类不同:sleep 是
Thread类的静态方法,可以在任何地方通过Thread.sleep()调用;
wait 是Object类的实例方法,必须通过对象实例调用。 - 锁释放情况不同:
sleep()不会释放锁;wait()会释放锁,进入等待状态,直到调用notify()才会去竞争锁。 - 使用条件不同:
sleep()可以在任意位置调用,无需获取锁;wait()必须在线程获取对应对象的锁后调用,否则会抛出异常。 - 唤醒后状态不同:
sleep()结束后线程直接进入RUNNABLE状态;wait()被唤醒后会进入BLOCKED状态,等待重新获取锁。
sleep会释放cpu吗?
会释放CPU,但是不会释放锁。
当线程调用 sleep() 后,会主动让出 CPU 时间片,进入 TIMED_WAITING 状态。
blocked和waiting有啥区别
- blocked是锁竞争导致的阻塞状态;waiting是人为触发的状态。
- blocked唤醒是自动的;waiting需要其他线程调用notify()或notifyAll()来唤醒。
wait 状态下的线程如何进行恢复到 runnable 状态?
当线程处于 wait 状态时,需要其他线程调用该线程 锁对象 的 notify() 或 notifyAll() 方法来唤醒它。
notify 和 notifyAll 的区别?
notify():随机唤醒一个线程,其他线程仍然处于WAITING状态。notifyAll():唤醒所有等待线程,所有线程都进入 BLOCKED 状态,等待重新获取锁。
notify 选择哪个线程?
具体选择哪个线程是由 JVM 决定的,比如 hotspot 实现中,就是先进先出的顺序。
不同的线程之间如何通信?
- 共享变量:通过共享内存中的变量进行通信,需要注意线程安全问题。
- 等待/通知机制:使用
wait()和notify()方法进行线程间通信。
如何停止一个线程?
通过协作式的逻辑控制线程终止。
- 通过共享标志位:在线程中定义一个 volatile 的 boolean 变量,线程在运行时检查这个值。
- 通过 interrupt:调用线程的
interrupt()方法,线程在适当的时候检查isInterrupted()状态并退出。 - 如果是
Future任务,可以调用cancel()方法取消任务。
Go 的协程和 Java 的线程有啥区别?
- Go 的协程是Go自己管理的;Java 的线程是由操作系统管理的。
- Go 的协程更轻量级;Java 的线程相对较重。
- 使用方式不同:Go 的协程通过
go关键字创建;Java 的线程通过Thread类或线程池创建。