基础
并行跟并发有什么区别?
并行是多核 CPU 上的多任务处理,多个任务在同一时间真正地同时执行。
并发是单核 CPU 上的多任务处理,多个任务在同一时间段内交替执行,通过时间片轮转实现交替执行,用于解决 IO 密集型任务的瓶颈。
你是如何理解线程安全的?
如果一段代码块或者一个方法被多个线程同时执行,还能够正确地处理共享数据,那么这段代码块或者这个方法就是线程安全的。
可以从三个要素来确保线程安全:
1.原子性:一个操作要么完全执行,要么完全不执行,不会出现中间状态。
2.可见性:当一个线程修改了共享变量,其他线程能够立即看到变化。
3.有序性:要确保线程不会因为死锁、饥饿、活锁等问题导致无法继续执行。
说说进程和线程的区别?
进程说简单点就是我们在电脑上启动的一个个应用。它是操作系统分配资源的最小单位。
线程是进程中的独立执行单元。多个线程可以共享同一个进程的资源,如内存;每个线程都有自己独立的栈和寄存器。
协程
协程被视为比线程更轻量级的并发单元,可以在单线程中实现并发执行,由我们开发者显式调度。
协程是在用户态进行调度的,避免了线程切换时的内核态开销。
线程间是如何进行通信的?
原则上可以通过消息传递和共享内存两种方法来实现。Java 采用的是共享内存的并发模型。
这个模型被称为 Java 内存模型,简写为 JMM,它决定了一个线程对共享变量的写入,何时对另外一个线程可见。当然了,本地内存是 JMM 的一个抽象概念,并不真实存在。
用一句话来概括就是:共享变量存储在主内存中,每个线程的私有本地内存,存储的是这个共享变量的副本。
线程 A 与线程 B 之间如要通信,需要要经历 2 个步骤:
线程 A 把本地内存 A 中的共享变量副本刷新到主内存中。
线程 B 到主内存中读取线程 A 刷新过的共享变量,再同步到自己的共享变量副本中
说说线程有几种创建方式?
实现两个类一个接口,Tread类,runabble,callable(可以看结果)接口
一个 8G 内存的系统最多能创建多少个线程?
理论上大约 8000 个。
创建线程的时候,至少需要分配一个虚拟机栈,在 64 位操作系统中,默认大小为 1M,因此一个线程大约需要 1M 的内存。
但 JVM、操作系统本身的运行就要占一定的内存空间,所以实际上可以创建的线程数远比 8000 少。
启动一个 Java 程序,你能说说里面有哪些线程吗?
首先是 main 线程,这是程序执行的入口。
然后是垃圾回收线程,它是一个后台线程,负责回收不再使用的对象。
还有编译器线程,比如 JIT,负责把一部分热点代码编译后放到 codeCache 中。
你平时有用过多线程吗?你在代码中是哪些场景用呢?
用得比较多,批量数据处理、异步任务处理、定时任务调度都需要用到多线程。
调用 start 方法时会执行 run 方法,那怎么不直接调用 run方法?
调用 start() 会创建一个新的线程,并异步执行 run() 方法中的代码。
直接调用 run() 方法只是一个普通的同步方法调用,所有代码都在当前线程中执行,不会创建新线程。没有新的线程创建,也就达不到多线程并发的目的。
线程有哪些常用的调度方法?

说说wait方法和notify方法?
当线程 A 调用共享对象的 wait() 方法时,线程 A 会被阻塞挂起,直到:
线程 B 调用了共享对象的 notify() 方法或者 notifyAll() 方法;
其他线程调用线程 A 的 interrupt() 方法,导致线程 A 抛出 InterruptedException 异常。
线程 A 调用共享对象的 wait(timeout)方法后,没有在指定的 timeout 时间内被其它
当线程 A 调用共享对象的 notify() 方法后,会唤醒一个在这个共享对象上调用 wait 系列方法被挂起的线程。
共享对象上可能会有多个线程在等待,具体唤醒哪个线程是随机的。
如果调用的是 notifyAll 方法,会唤醒所有在这个共享变量上调用 wait 系列方法而被挂起的线程。
说说 sleep 方法?
当线程 A 调用了 Thread 的 sleep 方法后,线程 A 会暂时让出指定时间的执行权。
指定的睡眠时间到了后该方法会正常返回,接着参与 CPU 调度,获取到 CPU 资源后可以继续执行。
说说yield方法?
yield() 方法的目的是让当前线程让出 CPU 使用权,回到就绪状态。但是线程调度器可能会忽略。
说说interrupt方法?
interrupt() 方法用于通知线程停止,但不会直接终止线程,需要线程自行处理中断标志。
常与 isInterrupted() 或 Thread.interrupted() 配合使用。
说说 stop 方法?
stop 方法用来强制停止线程,目前已经处于废弃状态,因为 stop 方法可能会在不一致的状态下释放锁,破坏对象的一致性。
线程有几种状态?
new 代表线程被创建但未启动;runnable 代表线程处于就绪或正在运行状态,由操作系统调度;blocked 代表线程被阻塞,等待获取锁;waiting 代表线程等待其他线程的通知或中断;timed_waiting 代表线程会等待一段时间,超时后自动恢复;terminated 代表线程执行完毕,生命周期结束。

什么是线程上下文切换?
线程上下文切换是指 CPU 从一个线程切换到另一个线程执行时的过程。
在线程切换的过程中,CPU 需要保存当前线程的执行状态,并加载下一个线程的上下文。
之所以要这样,是因为 CPU 在同一时刻只能执行一个线程,为了实现多线程并发执行,需要不断地在多个线程之间切换。
为了让用户感觉多个线程是在同时执行的, CPU 资源的分配采用了时间片轮转的方式,线程在时间片内占用 CPU 执行任务。当线程使用完时间片后,就会让出 CPU 让其他线程占用。
线程可以被多核调度吗?
多核处理器提供了并行执行多个线程的能力。每个核心可以独立执行一个或多个线程,操作系统的任务调度器会根据策略和算法,如优先级调度、轮转调度等,决定哪个线程何时在哪个核心上运行。
守护线程了解吗
了解,守护线程是一种特殊的线程,它的作用是为其他线程提供服务。
Java 中的线程分为两类,一种是守护线程,另外一种是用户线程。
JVM 启动时会调用 main 方法,main 方法所在的线程就是一个用户线程。在 JVM 内部,同时还启动了很多守护线程,比如垃圾回收线程。
守护线程和用户线程有什么区别呢?
区别之一是当最后一个非守护线程束时, JVM 会正常退出,不管当前是否存在守护线程,也就是说守护线程是否结束并不影响 JVM 退出。
换而言之,只要有一个用户线程还没结束,正常情况下 JVM 就不会退出。
线程间有哪些通信方式
线程之间传递信息的方式有多种,比如说使用 volatile 和 synchronized 关键字共享对象、使用 wait() 和 notify() 方法实现生产者-消费者模式、使用 Exchanger 进行数据交换、使用 Condition 实现线程间的协调等。
简单说说 volatile 和 synchronized 的使用方式?
多个线程可以通过 volatile 和 synchronized 关键字访问和修改同一个对象,从而实现信息的传递。
关键字 volatile 可以用来修饰成员变量,告知程序任何对该变量的访问均需要从共享内存中获取,并同步刷新回共享内存,保证所有线程对变量访问的可见性。
关键字 synchronized 可以修饰方法,或者同步代码块,确保多个线程在同一个时刻只有一个线程在执行方法或代码块。
wait() 和 notify() 方法的使用方式了解吗?
一个线程调用共享对象的 wait() 方法时,它会进入该对象的等待池,释放已经持有的锁,进入等待状态。
一个线程调用 notify() 方法时,它会唤醒在该对象等待池中等待的一个线程,使其进入锁池,等待获取锁。
Condition 也提供了类似的方法,await() 负责阻塞、signal() 和 signalAll() 负责通知。
通常与锁 ReentrantLock 一起使用,为线程提供了一种等待某个条件成真的机制,并允许其他线程在该条件变化时通知等待线程。
Exchanger 的使用方式了解吗?
Exchanger 是一个同步点,可以在两个线程之间交换数据。一个线程调用 exchange() 方法,将数据传递给另一个线程,同时接收另一个线程的数据。
CompletableFuture 的使用方式了解吗?
CompletableFuture 是 Java 8 引入的一个类,支持异步编程,允许线程在完成计算后将结果传递给其他线程。
请说说 sleep 和 wait 的区别?(补充)
sleep 会让当前线程休眠,不需要获取对象锁,属于 Thread 类的方法;wait 会让获得对象锁的线程等待,要提前获得对象锁,属于 Object 类的方法。
所属类不同
sleep() 方法专属于 Thread 类。
wait() 方法专属于 Object 类。
锁行为不同
如果一个线程在持有某个对象锁时调用了 sleep 方法,它在睡眠期间仍然会持有这个锁。
使用条件不同
sleep() 方法可以在任何地方被调用。
wait() 方法必须在同步代码块或同步方法中被调用,这是因为调用 wait() 方法的前提是当前线程必须持有对象的锁。否则会抛出 IllegalMonitorStateException 异常。
唤醒方式不同
调用 sleep 方法后,线程会进入 TIMED_WAITING 状态,即在指定的时间内暂停执行。当指定的时间结束后,线程会自动恢复到 RUNNABLE 状态,等待 CPU 调度再次执行。
调用 wait 方法后,线程会进入 WAITING 状态,直到有其他线程在同一对象上调用 notify 或 notifyAll 方法,线程才会从 WAITING 状态转变为 RUNNABLE 状态,准备再次获得 CPU 的执行权。
怎么保证线程安全?
线程安全是指在并发环境下,多个线程访问共享资源时,程序能够正确地执行,而不会出现数据不一致的问题。
为了保证线程安全,可以使用 synchronized 关键字对方法加锁,对代码块加锁。线程在执行同步方法、同步代码块时,会获取类锁或者对象锁,其他线程就会阻塞并等待锁。
如果需要更细粒度的锁,可以使用 ReentrantLock 并发重入锁等。
如果需要保证变量的内存可见性,可以使用 volatile 关键字。
对于简单的原子变量操作,还可以使用 Atomic 原子类。
对于线程独立的数据,可以使用 ThreadLocal 来为每个线程提供专属的变量副本。
对于需要并发容器的地方,可以使用 ConcurrentHashMap、CopyOnWriteArrayList 等。
