JUC基础知识
、
# 线程和进程的区别?
- 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
- 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)
# 并行和并发有什么区别?
现在都是多核CPU,在多核CPU下 并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU 并行是同一时间动手做多件事情的能力,4核CPU同时执行4个线程
# 创建线程的四种方式
在java中一共有四种常见的创建方式,分别是:继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程。通常情况下,我们项目中都会采用线程池的方式创建线程。
# Thread和Runnable的区别
Thread
和Runnable
的区别主要体现在以下三个方面:
Thread
是一个类,而Runnable
是一个接口。由于Java的单继承限制,如果某个类已经继承了其他父类,要实现多线程就只能通过实现
Runnable
接口的方式。
Runnable
是线程任务的顶级接口,而Thread
类其实是实现了Runnable
接口。- 无论是继承
Thread
还是实现Runnable
,都需要重写run()
方法定义线程执行逻辑。
Runnable
代表一个可执行的任务,Thread
是实际执行任务的线程。- 这种设计实现了任务与线程的松耦合:我们可以定义
Runnable
任务,然后交给Thread
或线程池执行,提高代码的灵活性。
# runnable 和 callable 有什么区别
- Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
- Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
# 线程的 run()和 start()有什么区别?
start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
run(): 封装了要被线程执行的代码,可以被调用多次。
# 线程两次调用start方法
java里的线程只能调用一次start方法,第二次调用会抛出IllegalThreadStateException 在java里面的线程生命周期包括六种状态,
- NEW表示线程已创建,但尚未调用
start()
, - RUNNABLE,在这个状态下呢,线程可能是正在运行,也可能是在等待CPU资源的分配,
- BROCKED,表示线程处于锁等待状态,
- WAITING,表示线程处于条件等待状态,当触发条件后会唤醒,比如wait对应的notify方法,
- TIMED_WAITING,它和WAITING的状态是相同的,只是多了一个超时条件触发机制,
- TERMINATED,表示线程的执行结束
当我们第一次调用start方法后,线程的状态可能会处于终止状态,或者非new状态,再调用一次start方法,相当于让这个正在运行的线程重新运行一遍,不管是从线程的安全性角度来看,还是说从线程本身的执行逻辑来看,它都是不合理的,因此啊为了避免这样的问题,在线程运行的时候,会先去判断当前线程的状态是否为NEW
# 线程包括哪些状态,状态之间是如何变化的/Java线程的生命周期
在JDK中的Thread类中的枚举State里面定义了6中线程的状态分别是:新建、可运行、终结、阻塞、等待和有限等待六种。
关于线程的状态切换情况比较多。我分别介绍一下
当一个线程对象被创建,但还未调用 start 方法时处于新建NEW状态,调用了 start 方法,就会由新建进入可运行RUNNABLE状态。如果线程内代码已经执行完毕,由可运行进入终结TERMINATED状态。当然这些是一个线程正常执行情况。
如果线程获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞BLOCKED,只有当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态
如果线程获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁等待WAITING状态,当其它持锁线程调用 notify() 或 notifyAll() 方法,会恢复为可运行状态
还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待TIMED_WAITING状态,不需要主动唤醒,超时时间到自然恢复为可运行状态
线程可以通过三种方法进入WAITING
状态:
Object.wait()
:等待其他线程调用notify()
或notifyAll()
。Thread.join()
:等待目标线程执行完毕。LockSupport.park()
:暂停当前线程。
线程进入等待状态,但会在指定的时间后自动唤醒。线程可以通过四种带有超时时间的方法进入TIMED_WAITING
状态:
Thread.sleep(long millis)
:睡眠指定时间。Object.wait(long timeout)
:等待指定时间。Thread.join(long millis)
:等待目标线程指定时间。LockSupport.parkNanos(long nanos)
:暂停当前线程指定时间。
# 线程的状态BLOCKED和WAITING有什么区别
BLOCKED和WAITING都是属于线程的阻塞等待状态,
BLOCKED状态是线程在竞争synchronized
同步锁失败时进入的被动阻塞状态。当多个线程尝试获取同一个synchronized
锁时,未竞争到锁的线程会被JVM自动置为BLOCKED状态。这种状态仅出现在同步锁竞争场景,且线程的唤醒完全依赖锁持有者释放锁(无需外部干预)。
WAITING状态是线程主动进入的等待状态,通过显式调用以下方法触发:
Object.wait()
Thread.join()
LockSupport.park()
进入WAITING状态的线程必须依赖特定方法唤醒(如Object.notify()
唤醒wait()
的线程,LockSupport.unpark()
唤醒park()
的线程),否则会持续等待。
BLOCKED和waiting这两个状态的最大区别有两个,
- 触发方式:BLOCKED是被动由JVM触发(锁竞争失败),WAITING是主动通过代码调用触发。
- 唤醒机制:BLOCKED线程的唤醒是自动的(锁释放后由JVM调度),WAITING线程必须依赖显式唤醒。
# 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?⭐⭐⭐
可以这么做,在多线程中有多种方法让线程按特定顺序执行,可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。
比如说:
使用join方法,T3调用T2,T2调用T1,这样就能确保T1就会先完成而T3最后完成
# notify()和 notifyAll()有什么区别?
notifyAll:唤醒所有wait的线程
notify:只随机唤醒一个 wait 线程
# java 中 wait 和 sleep 方法的不同?
共同点
- wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
不同点
- 方法归属不同
- sleep(long) 是 Thread 的静态方法
- 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
- 醒来时机不同
- 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
- wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
- 它们都可以被打断唤醒
- 锁特性不同(重点)
- wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
- wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
- 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)
# wait和sleep是否会触发锁的释放以及CPU资源的释放
object.wait()方法它会释放锁资源以及CPU资源,而thread.sleep方法不会释放锁资源,但是会释放CPU资源
wait()
必须在 synchronized
同步代码块或方法中使用,因为它依赖于对象的监视器锁(Monitor Lock)。
当线程调用wait()方法时,表示当前线程的工作已经处理完成了,意味着其他竞争同一共享资源的线程有机会去执行,但其他线程需要竞争锁资源,所以wait的方法必须要释放锁,否则就会导致死锁的一个问题,
thread.sleep()方法,只是让线程进入睡眠状态,而这个方法并没有强制要求加synchronized同步锁,而从它的功能和语义来说也没有这个必要,当然如果在一个synchronized同步代码块里面去调用,Thread.sleep()方法,也并不会触发锁的一个释放,
# wait、notify必须在synchronized代码块中使用)
wait()
和notify()
方法表示线程的阻塞与唤醒,唤醒需要线程之间的通信,而通信除了通过过管道流的方法还有通过共享变量的改变来进行,wait和notify的底层就是通过操作对象的监视器(monitor)来进行通信,而synchronized
提供了获取和释放监视器的机制。
为了防止wait和notify的错误使用,Jdk强制要求wait和notify写在synchronized中, 否则会抛出IllegalMonitorStateException
wait()
通常要在循环中调用,以应对虚假唤醒(spurious wakeup)
# 如何停止一个正在运行的线程?
- 使用stop方法强行终止(不推荐,方法已作废)
- 使用interrupt方法中断线程(修改中断状态,需要使用interrupt判断中断状态主动结束线程,如果线程是等待状态则会抛出InterruptedException )
Java线程本质是操作系统线程的封装,线程调度由操作系统决定。java中的start()
方法只是通知OS线程可被调度,但具体执行由OS的CPU调度算法控制。所以理论上来说,要在java里面去中断一个正在运行的线程,只能像类似于LINUX里使用kill命令去结束进程的方式,java 里提供了一个stop方法,可以去强制终止,但是这种方式是不安全的,因为有可能这个线程的任务还没有执行完成,导致出现运行结果不正确的现象,
要想安全的去中断一个正在运行的线程,可以使用java 提供的interrupt的方法,那么这个方法需要配合isInterrupted方法使用,这种方法并不是强制中断,而是告诉正在运行的线程你可以停止,不过是否要中断,取决于正在运行的线程,所以它能够保证线程运行结果的一个安全性,若线程处于阻塞状态(如sleep()
/wait()
),会抛出InterruptedException
。
# 什么是CompletableFuture
CompletableFuture 是 Java 8 引入的一个并发工具类,它提供了一种强大且灵活的方式来处理异步任务及其执行结果。在 CompletableFuture 出现之前,Java 主要依赖 Callable
和 Future
机制来获取异步线程的执行结果。但Future
是通过阻塞等待的方式来实现的,对性能不是很友好
相比之下,CompletableFuture
允许我们将耗时任务提交给线程池异步执行,同时继续处理其他任务。当异步任务完成后,CompletableFuture
会自动触发回调方法,我们可以在回调中处理异步任务的任务结果,从而避免了阻塞等待的问题。这种方式类似于响应式编程,能够更高效地利用系统资源。
此外,CompletableFuture
提供了丰富的组合方法(如 thenApply
、thenAccept
、thenRun
等),支持链式调用,使得异步任务的编排更加灵活和直观。这使得开发者能够以更简洁的方式编写复杂的异步逻辑,提升代码的可读性和可维护性。
CompletableFuture
是 JDK 1.8 引入的基于事件驱动的异步回调工具类,核心功能是在异步任务完成后触发后续动作,从而解决传统同步调用效率低的问题。
比如说在一个支付的业务逻辑里面,涉及到查询订单,支付和发送邮件通知这三个逻辑,那么这三个逻辑是按照顺序同步去实现的,调用线程必须完全阻塞,直到所有步骤串行执行完毕。而使用CompletableFuture ,调用线程仅需要注册回调(如thenAccept
),而不用阻塞等待异步任务执行完毕才能执行下一步操作
CompletableFuture 的提供了五种任务组合方式,可以把多个异步任务,组成一个具有先后关系的处理链,然后基于事件来驱动任务链的执行, 第一种是thenCombine,并行执行两个独立任务,双任务均完成后触发回调,可合并两者结果, 第二种是thenCompose,串行执行,形成任务链, 任务A完成后触发任务B,B依赖A的结果 第三种是thenAccept,接收前序任务结果,不返回任何新的计算值, 第四种是thenApply,和thenAccept一样,接收前序任务结果,但它是具有返回值的方法, 第五种是thenRun,纯回调触发,不接收前序任务结果,就是说第一个任务执行完成以后,只触发执行一个实现了RUNNABLE接口的一个任务, 所以CompletableFuture弥补原本Future的不足,使得程序可以在非阻塞的状态下去完成异步回调机制
方法 | 输入 | 输出 | 是否接收前序结果 | 是否产生新结果 | 典型用途 |
---|---|---|---|---|---|
thenCombine | 两个Future | 任意类型 | 是(两者) | 是 | 合并并行任务结果 |
thenCompose | 一个Future | Future | 是 | 是(Future) | 异步任务链 |
thenAccept | 一个Future | void | 是 | 否 | 最终结果消费 |
thenApply | 一个Future | 任意类型 | 是 | 是 | 数据转换 |
thenRun | 一个Future | void | 否 | 否 | 完成后触发副作用操作 |