从零开始学习JAVA多线程(二)

简介: 从零开始学习JAVA多线程(二)前面已经简单介绍进程和线程,为后续学习做铺垫。本文讨论多线程传参,Java多线程异常处理机制。多线程的参数传递 在传统开发过程中,我们习惯在调用函数时,将所需的参数传入其中,通过函数内部逻辑处理返回结果,大多情况下,整个过程均是由一条线程执行,排除运行不必要的的偶发性,似乎并不会出现意料之外的结果。

从零开始学习JAVA多线程(二)
前面已经简单介绍进程和线程,为后续学习做铺垫。本文讨论多线程传参,Java多线程异常处理机制。

  1. 多线程的参数传递

     在传统开发过程中,我们习惯在调用函数时,将所需的参数传入其中,通过函数内部逻辑处理返回结果,大多情况下,整个过程均是由一条线程执行,排除运行不必要的的偶发性,似乎并不会出现意料之外的结果。而在多线程环境下,在使用线程时需要对线程进行一些必要的初始化,线程对这些数据进行处理后返回结果,由于线程的运行和结束并不可控,线程传参变得复杂起来,本文就以上问题介绍三种常用的传递参数方式。
    

(一)构造方法传参

  在创建线程时,需要创建一个Thread类的或者其子类的实例,通过调用其start()方法执行run()方法中的代码块,在此之前,我们可以通过构造函数传递线程运行所需要的数据,并使用变量保存起来。代码如下:

复制代码
1 public class MyThread extends Thread {
2
3 //定义变量保存参数
4 private String msg;
5
6 public MyThread() {
7 }
8
9 public MyThread(String msg) {
10 this.msg = msg;
11 }
12
13 //多线程的入口
14 public void run() {
15 System.out.println("MyThread " + msg);
16 }
17
18 public static void main(String[] args) {
19 MyThread myThread = new MyThread("is running");
20 myThread.start();
21 }
22 }
复制代码
运行结果:

MyThread is running

Process finished with exit code 0

 这种方式的优点很明显:简单、安全。在线程运行之前数据已经准备完成,避免线程丢失数据,如果传递更复杂数据,可以定义集合或者类等数据结构。缺点就是传递比较多的参数时,这种方式会使构造方法过于复杂,为了避免这种情况可以通过类方法和变量传递参数

(二)变量和类方法传递参数

 在Thread实例类中定义需要传递的参数变量,并且定义一系列public的方法(或变量),在创建完Tread实例后通过调用方法给参数逐个赋值。上面的代码也可以通过定义setMsg()方法传递参数,代码如下:

复制代码
1 public class MyThread extends Thread {
2
3 //定义变量保存参数
4 private String msg;
5
6 public void setMsg(String msg) {
7 this.msg = msg;
8 }
9
10 public MyThread() {
11 }
12
13 public MyThread(String msg) {
14 this.msg = msg;
15 }
16
17 //多线程的入口
18 public void run() {
19 System.out.println("MyThread " + msg);
20 System.out.println(Thread.currentThread().getThreadGroup());
21 }
22
23 public static void main(String[] args) {
24 MyThread myThread = new MyThread();
25 myThread.setMsg("is running");
26 myThread.start();
27 }
28 }
复制代码
(三)通过回调函数传递数据

 以上线程传递参数最常用的两种方式,但是可以发现参数都在main方法中设置,然后Thread实例被动的接受参数,假如在线程运行中动态的获取参数,如在run()方法先获取三个随机数,通过Work类的process方法对这随机数求和,最后通过Data类的value值返回结果。此例看出在返回value之前,因为随机数的不确定性,我们并不能事先传递的值value。

View Code

上面代码中process()方法即是回调函数,实质上是一个事件函数。整个事件流程为:将求和对象work传入线程,线程执行过程中三个随机数的产生触发了求和事件,通过传递进来的work对象调用process()方法,最后将结果返回线程并在控制台输出。这种传递数据的方式是在上面两种传参的基础上进行了薄层封装,并没有直接将参数传递给线程,而是通过传递的对象进行逻辑处理之后将结果返回。
  1. Java多线程异常处理机制

     先来看对于未检查异常在run()方法中是如何处理的。
    
     
    
    运行结果:
    
     
    
     由上可以看出对于未检查异常,从线程将会直接宕掉,主线程继续运行。那么在主线程中能不能捕获到异常呢?我们直接将全部代码块try catch起来
    
     
    
     然后你会发现并没有什么卵用,主线程没有捕获到任何异常信息,和未检出异常如出一辙,从线程直接宕掉,主线程继续运行
    
     如果先检查异常呢?
    
      
    
     IDEA提示:
    
     
    
     结果还是让人失望,程序还没运行,IDEA已经提示报错,原来run()方法本身不支持抛出异常的,方法重写更不允许throws,所以run()方法不支持往外抛出异常。
    
     最后再试下能不能在run()方法中try catch捕获异常,
    
      
    
      运行结果:
    
     
    
      原来在run()方法中try catch是能捕捉到异常的。所以对于多线程已检查的异常我们可以通过try catch进行处理,而对于未检查的异常,如果没有处理,一旦抛出该线程立马宕掉,主线程则继续运行,那么未检查的异常也全部需要try catch吗?当然这也是一种方式。除此之外,Java还提供了异常处理器。
    

(一)Java异常处理器

在Java线程run()方法中,对于未检查异常,借助于异常处理器进行处理。异常处理器可以直接理解为异常处理的方法,下面为具体如何使用。

UncaughtExceptionHandler,是Thread的内部接口。

Thread内部有两个变量,用来记录异常处理器。

Thread内部也分别提供了它们的get/set方法,set()方法其实没什么特别,主要是用来设置这两个内部变量,重点在于它们的get()方法。

对于getUncaughtExceptionHandler方法,如果当前UncaughtExceptionHandler对象不为空,那么直接返回该对象,如果为空,返回该线程所属的线程组,由此得知,ThreadGroup实现了UncaughtExceptionHandler接口。

与此同时,内部实现了uncaughtException方法,

而对于getDefaultUncaughtExceptionHandler()方法,只是简单的返回内部对象。

至此,我们可以了解到Thread内部有两个异常处理器,分别提供了get/set方法,对于set方法只是单纯的设置异常处理器,对于get方法,getDefaultUncaughtExceptionHandler()方法直接获取处理器;getUncaughtExceptionHandler()方法,进行判空,如果非空直接返回,如果为空返回该线程所属的线程组,并且当前线程组是实现了Thread.UncaughtExceptionHandler接口,内部实现了public void uncaughtException(Thread t, Throwable e),其本质上它才是线程处理器。

对于defaultUncaughtExceptionHandler,表示应该程序默认的,整个程序可以使用的,它的get/set方法均为static修饰;对于uncaughtExceptionHandler,属于实例方法,也就是说每个线程可以拥有一个,简言之:每个线程都可以有一个uncaughtExceptionHandler,整个应用可以有一个defaultUncaughtExceptionHandler。它们之间是个体与全局的关系,如果个体拥有那么就不再使用全局的;否则,走全局。这样做的好处是非常灵活,既可以保证单个线程特别处理,又可以保障整个程序做到统一处理,在诸多场景发挥多种用处。

(二)异常处理逻辑

  当run()方法中发生异常,JVM调用异常分发器,也就是借助getUncaughtExceptionHandler方法获取异常处理器,然后执行它的uncaughtException方法。

 

 所以关键之处在于uncaughtException()方法,再来看它的源码:

View Code

如果已经设置异常处理器,那么直接返回,如果没有设置,返回当前线程组,并且调用线程组的uncaughtException方法时(如上图),如果该线程组重写了uncaughtException方法,直接调用;如果没有,调用该线程组的父线程组;如果父线程组仍然没有重写,调用爷爷线程组,以此类推。但是如果所有的线程组都没有重写,进入else里面,在else中获取默认处理器,如果默认有,执行uncaughtException方法,如果没有直接system.err。

以上就是异常处理器的处理逻辑,可以看出uncaughtExceptionHandler处理器优先级要高于defaultUncaughtException,这也符合就近原则,如果自身拥有了,又何必调用全局拥有的呢!

(三)异常处理器代码演示
View Code
运行结果:

以上示例可以看出,尽管为检查异常,通过异常处理器依然能够感知异常和信息获取,不会直接宕掉了。主要注意的是,必须在调用start()方法之前设置异常处理器,否则线程依旧直接宕掉。

(四)总结

  Java多线程异常处理机制依赖于Thread内部两个异常处理器,uncaughtExceptionHandler和defaultUncaughtExceptionHandler。两者之间为个体和全局之间的关系,如果前者已经设置,那么直接使用,否则使用后者。大致步骤为:

  a. 如果设置了异常处理器uncaughtExceptionHandler直接使用。

  b. 如果没设置,将会在祖先线程组中查找第一个重写了uncaughtException的线程组,然后调用他的uncaughtException方法

  c. 如果都没有重写,那么使用应用默认的全局异常处理器defaultUncaughtExceptionHandler

  d. 如果还是没有设置,直接标准错误打印信息

   如果想要设置线程特有的异常处理器,可以调用set方法进行设置;如果想要对全局进行设置,可以调用静态方法进行设置,需要注意的是必须要在调用start()方法之前设置。

原文地址https://www.cnblogs.com/supiaol/p/10531156.html

相关文章
|
8天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
8天前
|
消息中间件 前端开发 Java
java学习路径
【4月更文挑战第9天】java学习路径
17 1
|
9天前
|
安全 Java 开发者
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第9天】本文将深入探讨Java并发编程的核心概念,包括线程安全和性能优化。我们将详细解析Java中的同步机制,包括synchronized关键字、Lock接口以及并发集合等,并探讨它们如何影响程序的性能。此外,我们还将讨论Java内存模型,以及它如何影响并发程序的行为。最后,我们将提供一些实用的并发编程技巧和最佳实践,帮助开发者编写出既线程安全又高效的Java程序。
22 3
|
8天前
|
算法 Java 开发者
Java中的多线程编程:概念、实现与性能优化
【4月更文挑战第9天】在Java编程中,多线程是一种强大的工具,它允许开发者创建并发执行的程序,提高系统的响应性和吞吐量。本文将深入探讨Java多线程的核心概念,包括线程的生命周期、线程同步机制以及线程池的使用。接着,我们将展示如何通过继承Thread类和实现Runnable接口来创建线程,并讨论各自的优缺点。此外,文章还将介绍高级主题,如死锁的预防、避免和检测,以及如何使用并发集合和原子变量来提高多线程程序的性能和安全性。最后,我们将提供一些实用的性能优化技巧,帮助开发者编写出更高效、更稳定的多线程应用程序。
|
8天前
|
设计模式 前端开发 安全
Java是一种广泛使用的编程语言,其学习路径可以大致分为以下几个阶段
【4月更文挑战第9天】Java是一种广泛使用的编程语言,其学习路径可以大致分为以下几个阶段
12 1
|
6天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第11天】 在Java中,高效的并发编程是提升应用性能和响应能力的关键。本文将探讨Java并发的核心概念,包括线程安全、锁机制、线程池以及并发集合等,同时提供实用的编程技巧和最佳实践,帮助开发者在保证线程安全的前提下,优化程序性能。我们将通过分析常见的并发问题,如竞态条件、死锁,以及如何利用现代Java并发工具来避免这些问题,从而构建更加健壮和高效的多线程应用程序。
|
2天前
|
设计模式 运维 安全
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第15天】在Java开发中,多线程编程是提升应用程序性能和响应能力的关键手段。然而,它伴随着诸多挑战,尤其是在保证线程安全的同时如何避免性能瓶颈。本文将探讨Java并发编程的核心概念,包括同步机制、锁优化、线程池使用以及并发集合等,旨在为开发者提供实用的线程安全策略和性能优化技巧。通过实例分析和最佳实践的分享,我们的目标是帮助读者构建既高效又可靠的多线程应用。
|
4天前
|
Java 程序员 编译器
Java中的线程同步与锁优化策略
【4月更文挑战第14天】在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。Java提供了多种机制来实现线程同步,其中最常用的是synchronized关键字和Lock接口。本文将深入探讨Java中的线程同步问题,并分析如何通过锁优化策略提高程序性能。我们将首先介绍线程同步的基本概念,然后详细讨论synchronized和Lock的使用及优缺点,最后探讨一些锁优化技巧,如锁粗化、锁消除和读写锁等。
|
5天前
|
Java
探秘jstack:解决Java应用线程问题的利器
探秘jstack:解决Java应用线程问题的利器
14 1
探秘jstack:解决Java应用线程问题的利器
|
5天前
|
Java 调度 开发者
Java 21时代的标志:虚拟线程带来的并发编程新境界
Java 21时代的标志:虚拟线程带来的并发编程新境界
14 0