2w字 + 40张图带你参透并发编程!
Hollis
共 13304字,需浏览 27分钟
·
2020-08-24 03:28
并发历史
浪费资源
的情况。并发性
,操作系统使我们的程序能够同时运行多个程序,一个程序就是一个进程,也就相当于同时运行多个进程。并发系统
,并发性是操作系统非常重要的特征,操作系统具有同时处理和调度多个程序的能力,比如多个 I/O 设备同时在输入输出;设备 I/O 和 CPU 计算同时进行;内存中同时有多个系统和用户程序被启动交替、穿插地执行。操作系统在协调和分配进程的同时,操作系统也会为不同进程分配不同的资源。资源利用率
,我们上面说到,单个进程存在资源浪费的情况,举个例子,当你在为某个文件夹赋予权限的时候,输入程序无法接受外部的输入字符,只有等到权限赋予完毕后才能接受外部输入。总的来讲,就是在等待程序时无法执行其他工作。如果在等待程序时可以运行另一个程序,那么将会大大提高资源的利用率。(资源并不会觉得累)因为它不会划水~公平性
,不同的用户和程序都能够使用计算机上的资源。一种高效的运行方式是为不同的程序划分时间片来使用资源,但是有一点需要注意,操作系统可以决定不同进程的优先级。虽然每个进程都有能够公平享有资源的权利,但是当有一个进程释放资源后的同时有一个优先级更高的进程抢夺资源,就会造成优先级低的进程无法获得资源,进而导致进程饥饿。便利性
,单个进程是是不用通信的,通信的本质就是信息交换
,及时进行信息交换能够避免信息孤岛
,做重复性的工作;任何并发能做的事情,单进程也能够实现,只不过这种方式效率很低,它是一种顺序性
的。
串行编程
)也不是一无是处
的,串行编程的优势在于其「直观性和简单性」,客观来讲,串行编程更适合我们人脑的思考方式,但是我们并不会满足于顺序编程,「we want it more!!!」 。资源利用率、公平性和便利性促使着进程出现的同时,也促使着线程
的出现。轻量级
的进程,轻量级体现在线程的创建和销毁要比进程的开销小很多。线程
的探究。2什么是多线程
并发和并行的关系
并发
意味着应用程序会执行多个的任务,但是如果计算机只有一个 CPU 的话,那么应用程序无法同时执行多个的任务,但是应用程序又需要执行多个任务,所以计算机在开始执行下一个任务之前,它并没有完成当前的任务,只是把状态暂存,进行任务切换,CPU 在多个任务之间进行切换,直到任务完成。如下图所示并行
是指应用程序将其任务分解为较小的子任务,这些子任务可以并行处理,例如在多个CPU上同时进行。优势和劣势
java.util.concurrent
和跨平台的内存模型
,同时也提高了开发人员的门槛,并发一直以来是一个高阶的主题,但是现在,并发也成为了主流开发人员的必备素质。难以定位
是并发程序的一个特征,所以在此基础上你需要有扎实的并发基本功。那么,并发为什么会出现呢?并发为什么会出现
高速缓存
,高速缓存的访问速度要高于内存,最慢的是磁盘访问。CPU 使用缓存来中和和内存的访问速度差异 操作系统提供进程和线程调度,让 CPU 在执行指令的同时分时复用线程,让内存和磁盘不断交互,不同的 CPU 时间片
能够执行不同的任务,从而均衡这三者的差异编译程序提供优化指令的执行顺序,让缓存能够合理的使用
线程带来的安全性问题
同步机制
的情况下,多个线程中的执行操作往往是不可预测的,这也是多线程带来的挑战之一,下面我们给出一段代码,来看看安全性问题体现在哪public class TSynchronized implements Runnable{
static int i = 0;
public void increase(){
i++;
}
@Override
public void run() {
for(int i = 0;i < 1000;i++) {
increase();
}
}
public static void main(String[] args) throws InterruptedException {
TSynchronized tSynchronized = new TSynchronized();
Thread aThread = new Thread(tSynchronized);
Thread bThread = new Thread(tSynchronized);
aThread.start();
bThread.start();
System.out.println("i = " + i);
}
}
TSynchronized
实现了 Runnable 接口,并定义了一个静态变量 i
,然后在 increase
方法中每次都增加 i 的值,在其实现的 run 方法中进行循环调用,共执行 1000 次。1. 可见性问题
可见性
导致的线程安全问题。2. 原子性问题
aThread
和 bThread
交替执行产生了不同的结果。但是根源不是因为创建了两个线程导致的,多线程只是产生线程安全性的必要条件,最终的根源出现在 i++
这个操作上。i++
不是一个 原子性
操作,仔细想一下,i++ 其实有三个步骤,读取 i 的值,执行 i + 1 操作,然后把 i + 1 得出的值重新赋给 i(将结果写入内存)。读取/增加/写入
阶段产生线程切换,都会产生线程安全问题。例如如下图所示原子性
这个概念,那么什么是原子性呢?3. 有序性问题
有序性
问题,有序性顾名思义就是顺序性,在计算机中指的就是指令的先后执行顺序。一个非常显而易见的例子就是 JVM 中的类加载
4. 活跃性问题
活跃性
问题,如何定义活跃性问题呢?活跃性问题关注的是 「某件事情是否会发生」。互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程释放。 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持占有。 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。 循环等待:指在发生死锁时,必然存在一个进程对应的环形链。
痴情
的话,那么活锁
用一则成语来表示就是 弄巧成拙
。活锁(livelock)
。while(true){...}
for(;;){}
饥饿
,我们后面会说。5. 性能问题
性能
问题,如果说活跃性问题关注的是最终的结果,那么性能问题关注的就是造成结果的过程,性能问题有很多方面:比如「服务时间过长,吞吐率过低,资源消耗过高」,在多线程中这样的问题同样存在。线程切换
,也称为 上下文切换(Context Switch)
,这种操作开销很大。引起线程切换的几种方式
当前正在执行的任务完成,系统的 CPU 正常调度下一个需要运行的线程 当前正在执行的任务遇到 I/O 等阻塞操作,线程调度器挂起此任务,继续调度下一个任务。 多个任务并发抢占锁资源,当前任务没有获得锁资源,被线程调度器挂起,继续调度下一个任务。 用户的代码挂起当前任务,比如线程执行 sleep 方法,让出CPU。 使用硬件中断的方式引起上下文切换
共享(Shared)
的 和 可变(Mutable)
的状态。只有共享和可变的变量才会出现问题,私有变量不会出现问题,参考程序计数器
。不要在多线程之间共享变量 将共享变量置为不可变的
什么是线程安全性
竞态条件
。仅仅当多个线程共享资源时,才会出现竞态条件。原子性
原子性
操作想象成为一个不可分割
的整体,它的结果只有两种,要么全部执行,要么全部回滚。你可以把原子性认为是 婚姻关系
的一种,男人和女人只会产生两种结果,好好的
和 说散就散
,一般男人的一生都可以把他看成是原子性的一种,当然我们不排除时间管理(线程切换)
的个例,我们知道线程切换必然会伴随着安全性问题,男人要出去浪也会造成两种结果,这两种结果分别对应安全性的两个结果:线程安全(好好的)和线程不安全(说散就散)。竞态条件
public class RaceCondition {
private Signleton single = null;
public Signleton newSingleton(){
if(single == null){
single = new Signleton();
}
return single;
}
}
single
的时候,如果 single 判断为空,此时发生了线程切换,另外一个线程执行,判断 single 的时候,也是空,执行 new 操作,然后线程切换回之前的线程,再执行 new 操作,那么内存中就会有两个 Singleton 对象。加锁机制
synchronized
关键字,它有三种保护机制对方法进行加锁,确保多个线程中只有一个线程执行方法; 对某个对象实例(在我们上面的探讨中,变量可以使用对象来替换)进行加锁,确保多个线程中只有一个线程对对象实例进行访问; 对类对象进行加锁,确保多个线程只有一个线程能够访问类中的资源。
同步代码块(Synchronized Block)
,例如synchronized(lock){
// 线程安全的代码
}
内置锁(Instrinsic Lock)
或者 监视器锁(Monitor Lock)
。线程在进入同步代码之前会自动获得锁,并且在退出同步代码时自动释放锁,而无论是通过正常执行路径退出还是通过异常路径退出,获得内置锁的唯一途径就是进入这个由锁保护的同步代码块或方法。互斥
,互斥意味着独占
,最多只有一个线程持有锁,当线程 A 尝试获得一个由线程 B 持有的锁时,线程 A 必须等待或者阻塞,直到线程 B 释放这个锁,如果线程 B 不释放锁的话,那么线程 A 将会一直等待下去。public class Retreent {
public synchronized void doSomething(){
doSomethingElse();
System.out.println("doSomething......");
}
public synchronized void doSomethingElse(){
System.out.println("doSomethingElse......");
}
volatile
是一种轻量级的 synchronized
,也就是一种轻量级的加锁方式,volatile 通过保证共享变量的可见性来从侧面对对象进行加锁。可见性的意思就是当一个线程修改一个共享变量时,另外一个线程能够 看见
这个修改的值。volatile 的执行成本要比 synchronized
低很多,因为 volatile 不会引起线程的上下文切换。原子类
来保证线程安全,原子类其实就是 rt.jar
下面以 atomic
开头的类java.util.concurrent
工具包下的线程安全的集合类来确保线程安全,具体的实现类和其原理我们后面会说。竞态条件和关键区域
并发模型和分布式系统很相似
线程
彼此进行通信,而在分布式系统模型中是 进程
彼此进行通信。然而本质上,进程和线程也非常相似。这也就是为什么并发模型和分布式模型非常相似的原因。认识两个状态
共享状态
,是具有共享状态
还是独立状态
。共享状态也就意味着在不同线程之间共享某些状态数据
,比如一个或者多个对象。当线程要共享数据时,就会造成 竞态条件
或者 死锁
等问题。当然,这些问题只是可能会出现,具体实现方式取决于你是否安全的使用和访问共享对象。并发模型
并行 Worker
代理人(Delegator)
,然后由代理人把工作分配给不同的 工人(worker)
。如下图所示java.util.concurrent
包下的并发工具都使用了这种模型。1. 并行 Worker 的优点
异步
的。2. 并行 Worker 的缺点
竞态条件
,死锁
和许多其他共享状态造成的并发问题。阻塞
。可持久化的数据结构(Persistent data structures)
是另外一个选择。可持久化的数据结构在修改后始终会保留先前版本。因此,如果多个线程同时修改一个可持久化的数据结构,并且一个线程对其进行了修改,则修改的线程会获得对新数据结构的引用。链表(LinkedList)
在硬件性能上表现不佳。列表中的每个元素都是一个对象,这些对象散布在计算机内存中。现代 CPU 的顺序访问往往要快的多,因此使用数组等顺序访问的数据结构则能够获得更高的性能。CPU 高速缓存可以将一个大的矩阵块加载到高速缓存中,并让 CPU 在加载后直接访问 CPU 高速缓存中的数据。对于链表,将元素分散在整个 RAM 上,这实际上是不可能的。流水线
流水线并发模型
,下面是流水线设计模型的流程图非阻塞I/O
,也就是说,当没有给 worker 分配任务时,worker 会做其他工作。非阻塞I/O 意味着当 worker 开始 I/O 操作,例如从网络中读取文件,worker 不会等待 I/O 调用完成。因为 I/O 操作很慢,所以等待 I/O 非常耗费时间。在等待 I/O 的同时,CPU 可以做其他事情,I/O 操作完成后的结果将传递给下一个 worker。下面是非阻塞 I/O 的流程图1. 响应式 - 事件驱动系统
响应式
或者 事件驱动系统
,这种模型会根据外部的事件作出响应,事件可能是某个 HTTP 请求或者某个文件完成加载到内存中。2. Actor 模型
Actor
对接收到的消息做出响应,然后可以创建出更多的 Actor 或发送更多的消息,同时准备接收下一条消息。3. Channels 模型
通道(Channel)
上,然后其他 worker 可以在这些通道上获取消息,下面是 Channel 的模型图4. 流水线设计的优点
5. 流水线设计的缺点
回调地狱
。回调地狱很难追踪 debug。函数性并行
原子
操作。每个函数调用都可以独立于任何其他函数调用执行。ForkAndJoinPool
类就实现了函数性并行的功能。Java 8 提出了 stream 的概念,使用并行流也能够实现大量集合的迭代。顺序流
,在 Java 中,每一条 Java 线程就像是 JVM 的一条顺序流,就像是虚拟 CPU 一样来执行代码。Java 中的 main()
方法是一条特殊的线程,JVM 创建的 main 线程是一条主执行线程
,在 Java 中,方法都是由 main 方法发起的。在 main 方法中,你照样可以创建其他的线程
(执行顺序流),这些线程可以和 main 方法共同执行应用代码。java.lang.Thread
类或其子类的实例。那么下面我们就来一起探讨一下在 Java 中如何创建和启动线程。创建并启动线程
通过继承 Thread
类来创建线程通过实现 Runnable
接口来创建线程通过 Callable
和Future
来创建线程
继承 Thread 类来创建线程
public class TJavaThread extends Thread{
static int count;
@Override
public synchronized void run() {
for(int i = 0;i < 10000;i++){
count++;
}
}
public static void main(String[] args) throws InterruptedException {
TJavaThread tJavaThread = new TJavaThread();
tJavaThread.start();
tJavaThread.join();
System.out.println("count = " + count);
}
}
定义一个线程类使其继承 Thread 类,并重写其中的 run 方法,run 方法内部就是线程要完成的任务,因此 run 方法也被称为 执行体
创建了 Thread 的子类,上面代码中的子类是 TJavaThread
启动方法需要注意,并不是直接调用 run
方法来启动线程,而是使用start
方法来启动线程。当然 run 方法可以调用,这样的话就会变成普通方法调用,而不是新创建一个线程来调用了。
public static void main(String[] args) throws InterruptedException {
TJavaThread tJavaThread = new TJavaThread();
tJavaThread.run();
System.out.println("count = " + count);
}
join
方法,它用来等待线程的执行结束,如果我们不加 join 方法,它就不会等待 tJavaThread 的执行完毕,输出的结果可能就不是 10000
this
关键字直接指向当前线程,而无需使用 Thread.currentThread()
来获取当前线程。使用 Runnable 接口来创建线程
Runnable
接口来创建线程,如下示例public class TJavaThreadUseImplements implements Runnable{
static int count;
@Override
public synchronized void run() {
for(int i = 0;i < 10000;i++){
count++;
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new TJavaThreadUseImplements()).start();
System.out.println("count = " + count);
}
}
首先定义 Runnable 接口,并重写 Runnable 接口的 run 方法,run 方法的方法体同样是该线程的线程执行体。 创建线程实例,可以使用上面代码这种简单的方式创建,也可以通过 new 出线程的实例来创建,如下所示
TJavaThreadUseImplements tJavaThreadUseImplements = new TJavaThreadUseImplements();
new Thread(tJavaThreadUseImplements).start();
再调用线程对象的 start 方法来启动该线程。
Runnable
的同时也能实现其他接口,非常适合多个相同线程来处理同一份资源的情况,体现了面向对象的思想。Thread.currentThread()
方法。使用 Callable 接口来创建线程
Callable
接口而不是 Runnable 接口。Java SE5 引入了 Callable 接口,它的示例如下public class CallableTask implements Callable {
static int count;
public CallableTask(int count){
this.count = count;
}
@Override
public Object call() {
return count;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTasktask = new FutureTask((Callable ) () -> {
for(int i = 0;i < 1000;i++){
count++;
}
return count;
});
Thread thread = new Thread(task);
thread.start();
Integer total = task.get();
System.out.println("total = " + total);
}
}
Callable 执行的任务有返回值,而 Runnable 执行的任务没有返回值 Callable(重写)的方法是 call 方法,而 Runnable(重写)的方法是 run 方法。 call 方法可以抛出异常,而 Runnable 方法不能抛出异常
使用线程池来创建线程
Executor
,Executor 虽然不是传统线程创建的方式之一,但是它却成为了创建线程的替代者,使用线程池的好处如下利用线程池能够复用线程、控制最大并发数。 实现任务线程队列 缓存策略
和拒绝机制
。实现某些与时间相关的功能,如定时执行、周期执行等。 隔离线程环境。比如,交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要大;因此,通过配置独立的线程池,将较慢的交易服务与搜索服务隔开,避免服务线程互相影响。
new Thread(new(RunnableTask())).start()
// 替换为
Executor executor = new ExecutorSubClass() // 线程池实现类;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
ExecutorService
是 Executor 的默认实现,也是 Executor 的扩展接口,ThreadPoolExecutor 类提供了线程池的扩展实现。Executors
类为这些 Executor 提供了方便的工厂方法。下面是使用 ExecutorService 创建线程的几种方式1. CachedThreadPool
异步
任务的执行,而无须显示地管理线程的生命周期。public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0;i < 5;i++){
service.execute(new TestThread());
}
service.shutdown();
}
CachedThreadPool
会为每个任务都创建一个线程。Executors
创建的,这个方法可以确定 Executor 类型。对 shutDown
的调用可以防止新任务提交给 ExecutorService ,这个线程在 Executor 中所有任务完成后退出。2. FixedThreadPool
有限
的线程集来启动多线程public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(5);
for(int i = 0;i < 5;i++){
service.execute(new TestThread());
}
service.shutdown();
}
3. SingleThreadExecutor
线程数量为 1
的 FixedThreadPool,如果向 SingleThreadPool 一次性提交了多个任务,那么这些任务将会排队,每个任务都会在下一个任务开始前结束,所有的任务都将使用相同的线程。SingleThreadPool 会序列化所有提交给他的任务,并会维护它自己(隐藏)的悬挂队列。public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for(int i = 0;i < 5;i++){
service.execute(new TestThread());
}
service.shutdown();
}
走完
这条线程的执行路径。你可以用 SingleThreadExecutor 来确保任意时刻都只有唯一一个任务在运行。休眠
sleep()
方法, 一般使用的TimeUnit
这个时间类替换 Thread.sleep()
方法,示例如下:public class SuperclassThread extends TestThread{
@Override
public void run() {
System.out.println(Thread.currentThread() + "starting ..." );
try {
for(int i = 0;i < 5;i++){
if(i == 3){
System.out.println(Thread.currentThread() + "sleeping ...");
TimeUnit.MILLISECONDS.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "wakeup and end ...");
}
public static void main(String[] args) {
ExecutorService executors = Executors.newCachedThreadPool();
for(int i = 0;i < 5;i++){
executors.execute(new SuperclassThread());
}
executors.shutdown();
}
}
优先级
public class SimplePriorities implements Runnable{
private int priority;
public SimplePriorities(int priority) {
this.priority = priority;
}
@Override
public void run() {
Thread.currentThread().setPriority(priority);
for(int i = 0;i < 100;i++){
System.out.println(this);
if(i % 10 == 0){
Thread.yield();
}
}
}
@Override
public String toString() {
return Thread.currentThread() + " " + priority;
}
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0;i < 5;i++){
service.execute(new SimplePriorities(Thread.MAX_PRIORITY));
}
service.execute(new SimplePriorities(Thread.MIN_PRIORITY));
}
}
Thread.toString()
方法来打印线程的名称。你可以改写线程的默认输出,这里采用了 「Thread[pool-1-thread-1,10,main]」 这种形式的输出。作出让步
yield()
方法,实际上, yield() 方法经常被滥用。后台线程
后台(daemon)
线程,是指运行时在后台提供的一种服务线程,这种线程不是属于必须的。当所有非后台线程结束时,程序也就停止了,**同时会终止所有的后台线程。**反过来说,只要有任何非后台线程还在运行,程序就不会终止。public class SimpleDaemons implements Runnable{
@Override
public void run() {
while (true){
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + " " + this);
} catch (InterruptedException e) {
System.out.println("sleep() interrupted");
}
}
}
public static void main(String[] args) throws InterruptedException {
for(int i = 0;i < 10;i++){
Thread daemon = new Thread(new SimpleDaemons());
daemon.setDaemon(true);
daemon.start();
}
System.out.println("All Daemons started");
TimeUnit.MILLISECONDS.sleep(175);
}
}
daemon
是后台线程,无法影响主线程的执行。daemon.setDaemon(true)
去掉时,while(true) 会进行无限循环,那么主线程一直在执行最重要的任务,所以会一直循环下去无法停止。ThreadFactory
class SimpleThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
ThreadFactory
是一个接口,它只有一个方法就是创建线程的方法public interface ThreadFactory {
// 构建一个新的线程。实现类可能初始化优先级,名称,后台线程状态和 线程组等
Thread newThread(Runnable r);
}
public class DaemonThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
public class DaemonFromFactory implements Runnable{
@Override
public void run() {
while (true){
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + " " + this);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool(new DaemonThreadFactory());
for(int i = 0;i < 10;i++){
service.execute(new DaemonFromFactory());
}
System.out.println("All daemons started");
TimeUnit.MILLISECONDS.sleep(500);
}
}
Executors.newCachedThreadPool
可以接受一个线程池对象,创建一个根据需要创建新线程的线程池,但会在它们可用时重用先前构造的线程,并在需要时使用提供的 ThreadFactory 创建新线程。public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue(),
threadFactory);
}
加入一个线程
join()
方法,其效果是等待一段时间直到第二个线程结束才正常执行。如果某个线程在另一个线程 t 上调用 t.join() 方法,此线程将被挂起,直到目标线程 t 结束才回复(可以用 t.isAlive() 返回为真假判断)。interrupted
方法,这时需要用到 try...catch 子句public class TestJoinMethod extends Thread{
@Override
public void run() {
for(int i = 0;i < 5;i++){
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted sleep");
}
System.out.println(Thread.currentThread() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoinMethod join1 = new TestJoinMethod();
TestJoinMethod join2 = new TestJoinMethod();
TestJoinMethod join3 = new TestJoinMethod();
join1.start();
// join1.join();
join2.start();
join3.start();
}
}
线程异常捕获
public class ExceptionThread implements Runnable{
@Override
public void run() {
throw new RuntimeException();
}
public static void main(String[] args) {
try {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new ExceptionThread());
}catch (Exception e){
System.out.println("eeeee");
}
}
}
Thread.UncaughtExceptionHandler
,它允许你在每个 Thread 上都附着一个异常处理器。Thread.UncaughtExceptionHandler.uncaughtException()
会在线程因未捕获临近死亡时被调用。public class ExceptionThread2 implements Runnable{
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("run() by " + t);
System.out.println("eh = " + t.getUncaughtExceptionHandler());
// 手动抛出异常
throw new RuntimeException();
}
}
// 实现Thread.UncaughtExceptionHandler 接口,创建异常处理器
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught " + e);
}
}
public class HandlerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
System.out.println(this + " creating new Thread");
Thread t = new Thread(r);
System.out.println("created " + t);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("ex = " + t.getUncaughtExceptionHandler());
return t;
}
}
public class CaptureUncaughtException {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool(new HandlerThreadFactory());
service.execute(new ExceptionThread2());
}
}
UncaughtExceptionHandler
,你可以看到,未捕获的异常是通过 uncaughtException
来捕获的。有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️
评论