打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Java并发入门原理教程(一)
1、并发编程简介
1. 前言
大家好,本节我们来一起学习 Java 并发编程的核心原理。
作为本专题的第一个小节,我们先来了解下什么是并发编程,以及学习并发编程的必要性,及学习过程应该注意的事项。
下面,我们先了解一下 Java 并发编程。
2. 什么是并发编程?
所谓并发编程是指在一台处理器上 “同时” 处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。
并发编程,从程序设计的角度来说,是希望通过某些机制让计算机可以在一个时间段内,执行多个任务。从计算机 CPU 硬件层面来说,是一个或多个物理 CPU 在多个程序之间多路复用,提高对计算机资源的利用率。从调度算法角度来说,当任务数量多于 CPU 的核数时,并发编程能够通过操作系统的任务调度算法,实现多个任务一起执行。
3. 并发编程的重要性
对于一个 Java 程序员而言,能否熟练掌握并发编程是判断他优秀与否的重要标准之一。因为并发编程是 Java 语言中最为晦涩的知识点,它涉及操作系统、内存、CPU、编程语言等多方面的基础能力,更为考验一个程序员的内功。
并发编程在开发语言中占据着不可替代的位置。
4. 并发编程的特性
并发编程有三大特性:
原子性;
可见性;
有序性。
Tips: 后续的课程知识中,会对这些特性进行详细的详解。了解并掌握并发编程的三大特性,非常重要。
5. 为什么学习并发编程?
可以这样来说,在目前开发市场对于程序员的硬性要求中,并发编程占据了重要的位置,不懂并发编程的从业者不是一名合格的软件工程师。
尤其是大数据时代的来临,高并发更成为了家常便饭,工作中,你总是绕不开并发编程的任务,比如说,你想写个程序,一边从文件中读取数据,一边还要做实时计算… 所以,想成为一名资深的 Java 后端工程师,并发编程必须要牢牢把握。
6. 本套课程 JDK 版本
本套课程使用的 JDK 1.8 的版本。在进行课程代码实践的过程中,推荐学习者至少使用 JDK 1.8 及以上版本。
Tips:如果有学习者使用 JDK 1.8 以下的版本,那至少要保证 JDK 版本高于 1.5。因为我们课程的 Lock 接口部分,是 JDK 1.5 版本之后的新特性,所以要至少保证JDK 版本高于 1.5。
7. 学习基础
在开始学习并发编程之前,学习者需要掌握 JavaSE 的知识,这是学习并发编程的语言基础,也是 Java 程序员必备的基本功。
2、操作系统的并发
1. 前言
本节内容是从操作系统的层面谈并发,本节课程我们需要掌握如下内容:
掌握并发编程的定义,并发编程的定义是了解并发的前提基础;
从 CPU 谈并发诞生的意义,一切语言的基础都是操作系统,CPU 的并发与 Java 并发息息相关,了解 CPU 对学习并发编程至关重要;
操作系统,进程,线程之间的联系。本节课程的重点之一,三者之间的联系有助于对整体上有更加透彻的了解(重点);
了解什么是进程和线程以及两者之间的区别。起初学习并发编程的同学对两者之间的区别区分不明确,进程与线程的区别也是本节课程的重点之一(重点);
理解串行,并行,并发三种线程执行方式的区别,不同的执行方式会有不同的执行效果。
2. 并发编程的定义
定义:所谓并发编程是指在一台处理器上 “同时” 处理多个任务。并发是在同一实体上的多个事件,多个事件在同一时间间隔发生。
意义:开发者通过使用不同的语言,实现并发编程,充分的利用处理器(CPU)的每一个核,以达到最高的处理性能,提升服务器的资源利用率,提升数据的处理速度。
3. 从 CPU 谈并发编程
首先看下图,图中展示了最简单的 CPU 核心通过缓存与主存进行通讯的模型。
在缓存出现后不久,系统变得越来越复杂,缓存与主存之间的速度差异被拉大,由于 CPU 的频率太快了,快到主存跟不上,这样在线程处理器时钟周期内,CPU 常常需要等待主存,这样就会浪费资源。从我们的感官上,计算机可以同时运行多个任务,但是从 CPU 硬件层面上来说,其实是 CPU 执行线程的切换,由于切换频率非常快,致使我们从感官上感觉计算机可以同时运行多个程序。为了避免长时间的线程等待,我们一方面提升硬件指标(如多级高速缓存的诞生,这里不做讨论),另一方面引入了并发概念,充分的利用处理器(CPU)的每一个核,减少 CPU 资源等待的时间,以达到最高的处理性能。
4. 操作系统,进程,线程之间的联系与区别
我们首先来看看,三者之间的关系,从图中可以看到,操作系统是包含多个进程的容器,而每个进程又是容纳多个线程的容器。
什么是进程?官方定义:进程(baiProcess)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
Tips:系统进行资源分配和调度的基本单位其实就是 CPU 时间片的切换,一个 CPU 同一时间只能操作一个任务,只不过 CPU 在不停的切换工作任务,这里的时间片就是我们所说的系统进行资源分配和调度的基本单位。
那么从定义上感觉非常的抽象,但是进程其实就在我们日常的计算机使用过程中。请看下图,进入任务管理器看 Windows 操作系统下的进程:
什么是线程?官方定义: 线程是操作系统能够进行资源调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,每个线程执行的都是进程代码的某个片段,特定的线程总是在执行特定的任务。
线程与进程的区别?
诞生起源:先有进程,后有线程。进程由于资源利用率、公平性和便利性诞生。处理器的速度往往比外设的速度快(键盘、鼠标等),为了提高 CPU 的利用率,诞生了线程,目的就是为了提高程序的执行效率;
概念:进程是资源分配的最小单位。线程是程序执行的最小单位(线程是操作系统能够进行资源调度的最小单位,同个进程中的线程也可以被同时调度到多个 CPU 上运行),线程也被称为轻量级进程;
内存共享:默认情况下,进程的内存无法与其他进程共享(进程间通信通过 IPC 进行)。线程共享由操作系统分配给其父进程的内存块。
5. 串行,并行与并发
串行:顺序执行,按步就搬。在 A 任务执行完之前不可以执行 B。
并行:同时执行,多管齐下。指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同 CPU 核心上同时执行。
并发:穿插执行,减少等待。指多个线程轮流穿插着执行,并发的实质是一个物理 CPU 在若干道程序之间多路复用,其目的是提高有限物理资源的运行效率。
6. 小结
本节课程重点讲解了操作系统的并发原理,以及进程和线程之间的区别与联系。除此之外,理解串行,并行,并发三种线程执行方式的区别尤为重要,对以后的课程学习有很大的帮助。
3、Java 线程内存模型
1. 前言
本节内容是从操作系统的层面谈并发,本节课程我们需要掌握如下内容:
了解 Java 的内存模型定义,是 Java 并发编程基本原理的基础知识;
从概念上了解线程的私有内存空间和主内存,能够从全局上了解线程是如何进行内存数据的存取操作的;
了解线程拥有私有空间的意义,私有空间能够为线程提供独有的数据,其他线程不可干扰;
在多线程环境下,主内存操作共享变量需要注意的事项需谨记,数据安全问题很重要;
Java 线程也是拥有生命周期的,了解它的生命周期,从宏观上了解线程。
2. 什么是 Java 的内存模型
定义:Java 内存模型(即 Java Memory Model,简称 JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
3. Java 线程的私有内存和主内存
首先看下图,图中展示了Java 的内存模型。
工作内存(私有):由于JVM 运行程序的实体是线程,而每个线程创建时 JVM 都会为其创建一个工作内存(栈空间),用于存储线程私有的数据。线程私有的数据只能供自己使用,其他线程不能够访问到当前线程私有的内存空间,保证了不同的线程在处理自己的数据时,不受其他线程的影响。
主内存(共享):Java 内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问。从上图中可以看到,Java 的并发内存模型与操作系统的 CPU 运行方式极其相似,这就是 Java 的并发编程模型。通过创建多条线程,并发的进行操作,充分利用系统资源,达到高效的并发运算。
4. 线程拥有私有空间的意义
我们知道,线程的私有空间中存储的数据,仅供当前线程自己使用,其他线程不能够对数据进行访问。线程的私有空间会存放很多程序运行时所必须的数据,如:
程序计数器:记录当前方法执行到了哪里,以便 CPU 切换回来之后能够继续执行上次执行到的位置,而不会进行重复执行或者遗漏。
局部变量:局部变量是方法中的变量,只供当前方法使用。
方法参数:Java 方法会定义自己的入参,入参的真实值也会记录到内存空间供当前线程使用。
由于线程的内存空间会存放很多数据,这里只提以上三中数据以供同学理解线程私有空间的意义。为了加深理解,我们一起看一个简单的代码示例并进行分析:
public class DemoTest{    public static void main(String[] args) {        sum(10); // 解析点 3   }    public static void  sum(int num) {        int i = 5; // 解析点 1        set(); //解析点 2        System.out.println("num+i = "+ (num + i));   }    public static void  set() {        int  i = 100;   }}
在给出结果之前,我们来分析下:解析点 1 :设置 i 的值为 5;解析点 2:调用 set() 方法,逻辑如下。
public static void  set() {        int  i = 100;   }代码块123
那最终的结果是多少呢?解析点 3:我们传入的 sum 的参数值是 10,如果想确定结果,只要确定另外一个加数 i 的值就行了。我们通过分析,在方法 sum(int num) 中的 int i = 5 与方法 set() 中的 int i = 100 是两个不同的方法的局部变量,属于线程私有的。互相不会影响,所以set() 方法中的 int i = 100 不会影响最终的结果:
num+i = 15代码块1
5. 主内存操作共享变量需要注意的事项
确定是否是多线程环境:多线程环境下操作共享变量需要考虑线程的安全性;
确定是否有增删改操作:多线程环境下,如果对共享数据有增加,删除或者修改的操作,需要谨慎。为了保证线程的同步性,必须对该共享数据进行加锁操作,保证多线程环境下,所有的线程能够获取到正确的数据。如生产者与消费者模型,售票模型。这些会在后续章节进行代码实战演练;
多线程下的读操作:如果是只读操作,对共享数据不需要进行锁操作,因为数据本身未发生增删改操作,不会影响获取数据的准确性。
6. Java 线程的生命周期
每个事物都有其生命周期,也就是事物从出生开始到最终消亡这中间的整个过程。在其整个生命周期的历程中,会有不同阶段,每个阶段对应着一种状态,比如:人的一生会经历从婴幼儿、青少年、青壮年、中老年到最终死亡,离开这人世间,这是人一生的状态。
同样的,线程作为一种事物,也有生命周期,在其生命周期中也存在着不同的状态,不同的状态之间还会有互相转换。
Java 线程的声明周期会经历 6 中不同的状态变化,后续章节会有详细描述。从线程的创建到线程执行任务的完成,即 Java 线程的生命周期。
7. 小结
Java 并发理论基础是基于Java 的内存模型的,了解 Java 内存模型,能够更有助于后续对并发知识的理解和运用了。Java 的内存模型是并发原理的基础,在了解内存模型的基础上去理解共享内存和私有内存,了解不同内存状态以及 Java 线程的生命周期至关重要。
4、Java 多线程的创建
1. 前言
本节内容重点需要掌握 Java 多线程的三种创建方式,具体内容如下:
Java 线程类 Thread 继承结构,这是 JDK Thread 源码的类结构,是了解 Thread 类的第一步;
掌握多线程的三种创建方式,这是本节的重点内容。本节所有内容都是围绕改话题进行的讲解;
了解多线程不同创建方式的优劣,不同的创建方式有自己的优势和劣势,本节还会推荐同学使用第二种接口实现的创建方式;
掌握 Thread 类常用方法,这也是本节的重点内容,其常用方法使我们开发过程中经常涉及到的,必须要熟记于心;
Thread 类编程实验实战,学习完多线程的创建方式,我们需要进行实战代码巩固本节的内容。
2. Thread 类结构介绍
介绍:位于 java.lang 包下的 Thread 类是非常重要的线程类。学习 Thread 类的使用是学习多线程并发编程的基础。它实现了 Runnable 接口,其包集成结构如下图所示。
3. 多线程的三种创建方式
Java 多线程有 3 种创建方式如下:
方式一:继承 Thread 类的方式创建线程;
方式二:实现 java.lang.Runnable 接口;
方式三:实现 Callable 接口。
在接下来的内容中,会详细的对这 3 种创建方式进行详细的讲解。
4. 多线程实现之继承 Thread 类
实现步骤:
步骤 1:继承 Thread 类 extends Thread;
步骤 2:复写 run () 方法,run () 方法是线程具体逻辑的实现方法。
实例:
/** * 方式一:继承Thread类的方式创建线程 */public class ThreadExtendTest extends Thread{ //步骤 1    @Override    public void run() { //步骤 2    //run方法内为具体的逻辑实现        System.out.println("create thread by thread extend");   }    public static void main(String[] args) {        new ThreadExtendTest(). start();   }}
5. 多线程实现之实现 Runnable 接口
Tips:由于 Java 是面向接口编程,且可进行多接口实现,相比 Java 的单继承特性更加灵活,易于扩展,所以相比方式一,更推荐使用方式二进行线程的创建。
实现步骤:
步骤 1:实现 Runnable 接口,implements Runnable;
步骤 2:复写 run () 方法,run () 方法是线程具体逻辑的实现方法。
实例:
/** * 方式二:实现java.lang.Runnable接口 */public class ThreadRunnableTest implements Runnable{//步骤 1    @Override    public void run() {//步骤 2 //run方法内为具体的逻辑实现        System.out.println("create thread by runnable implements");   }    public static void main(String[] args) {        new Thread(new ThreadRunnableTest()). start();   }}
6. 多线程实现之实现 Callable 接口
Tips:方式一与方式二的创建方式都是复写 run 方法,都是 void 形式的,没有返回值。但是对于方式三来说,实现 Callable 接口,能够有返回值类型。
实现步骤:
步骤 1:实现 Callable 接口,implements Callable;
步骤 2:复写 call () 方法,call () 方法是线程具体逻辑的实现方法。
实例:
/** * 方式三:实现Callable接口 */public class ThreadCallableTest implements Callable<String> {//步骤 1    @Override    public String call() throws Exception { //步骤 2    //call 方法的返回值类型是 String    //call 方法是线程具体逻辑的实现方法        return "create thread by implements Callable";   }    public static void main(String[] args) throws ExecutionException, InterruptedException{        FutureTask<String> future1 = new FutureTask<String>(new ThreadCallableTest());        Thread thread1 = new Thread(future1);        thread1. start();        System.out.println(future1.get());   }}
7. 匿名内部类创建 Thread
首先确认,这并不是线程创建的第四种方式,先来看如何创建。
实例:
Thread t = new Thread(new Runnable() {            @Override            public void run() {                System.out.println("通过匿名内部类创建Thread");           }       });
我们从代码中可以看出,还是进行了一个 Runnable 接口的使用,所以这并不是新的 Thread 创建方式,只不过是通过方式二实现的一个内部类创建。
Tips:在后续章节讲解 join 方法如何使用 的时候,我们会采用匿名内部类的方式进行多线程的创建。
8. Thread 类的常用方法介绍
方法作用
start()启动当前的线程,调用当前线程的 run ()。
run()通常需要重写 Thread 类中的此方法,将创建要执行的操作声明在此方法中。
currentThread()静态方法,返回代码执行的线程。
getName()获取当前线程的名字。
setName()设置当前线程的名字。
sleep(long millitime)让当前进程睡眠指定的毫秒数,在指定时间内,线程是阻塞状态。
isAlive()判断进程是否存活。
wait()线程等待。
notify()线程唤醒。
9. Thread 编程测验实验
实验目的:对 Thread 的创建方式进行练习,巩固本节重点内容,并在练习的过程中,使用常用的 start 方法和 sleep 方法以及 线程的 setName 方法。
实验步骤:
使用 Runnable 接口创建两条线程 :t1 和 t2;
请设置线程 t1 和 t2 的线程名称分别为 “ThreadOne” 和 “ThreadTwo”;
线程 t1 执行完 run () 方法后,线程睡眠 5 秒;
线程 t2 执行完 run () 方法后,线程睡眠 1 秒。
请先自行实现,并将结果与所提供的答案进行复核。
public class ThreadTest implements Runnable{    @Override    public void run() {        System.out.println("线程:"+Thread.currentThread()+" 正在执行...");   }    public static void main(String[] args) throws InterruptedException {        Thread t1 = new Thread(new ThreadTest());        t1.setName("ThreadOne");        Thread t2 = new Thread(new ThreadTest());        t2.setName("ThreadTwo");        t1. start();        t1.sleep(5000);        t2. start();        t1.sleep(1000);        System.out.println("线程执行结束。");   }}
执行结果:
线程:Thread[ThreadOne,5,main] 正在执行...线程:Thread[ThreadTwo,5,main] 正在执行...线程执行结束。代码块123
Tips:该测验主要针对线程的创建方式以及线程的执行 start 方法的测验,并附带进行了线程 setName 和线程 sleep 方法的使用。对于线程其他常用方法的使用如 wait 方法等,会在后续小节进行详细讲解。
10. 小结
本节课程的重中之重就是掌握线程的 3 中创建方式以及 Thread 类常用方法的使用,一定要掌握并吃透。
线程 Thread 的创建方式以及执行方式是学习多线程并发的前提条件,我们在使用无返回值的多线程创建方式时,推荐使用方式二进行多线程的创建。如果针对具体的业务场景需要使用多线程执行结果的返回值,那我们需要使用方式三进行线程的创建。
多线程 join 方法
1. 前言
本节对 join 方法进行深入的剖析,主要内容点如下:
了解 join 方法的作用,初步的理解 join 方法的使用带来的效果是学习本节内容的基础;
了解 join 方法异常处理,我们在使用 join 方法是,需要对 join 方法进行有效的异常处理;
通过匿名内部类创建Thread 是我们本节代码示例所使用的的方式,对这种方式的掌握在后续工作中非常重要;
掌握 join 方法如何使用,这是本节的重点内容,也是本节的核心内容;
掌握带参数的 join 方法使用,在后续开发过程中,如果对接口的最大返回时间有要求的话,某些情况下会用到带参数的 join 方法,次重点内容。
2. join 方法的作用
方法定义:多线程环境下,如果需要确保某一线程执行完毕后才可继续执行后续的代码,就可以通过使用 join 方法完成这一需求设计。
在项目实践中经常会遇到一个场景,就是需要等待某几件事情完成后主线程才能继续往下执行, 比如多个线程加载资源, 需要等待多个线程全部加载完毕再汇总处理。
Thread 类中有一个 join 方法就可以做这个事情,join 方法是 Thread 类直接提供的。join 是无参且返回值为 void 的方法。
如上图所示,假如有 3 个线程执行逻辑,线程 1 需要执行5秒钟,线程 2 需要执行10 秒钟,线程 3 需要执行 8 秒钟。如果我们的开发需求是:必须等 3 条线程都完成执行之后再进行后续的代码处理,这时候我们就需要使用到 join 方法。
使用 join 方法后:
第 5 秒钟:线程 1 执行完毕;线程 2 执行了一半;线程 3 还差 3 秒执行完毕;
第 8 秒钟:线程 1 等待了 3 秒;线程 3 执行完毕;线程 2 还差 2 秒执行完毕;
第10 秒钟:线程 1 等待了 5 秒;线程 3 等待了 2 秒;线程 2 执行完毕;
从线程 2 执行结束的那一刻:三条线程同时进行后续代码的执行。
这就是 join 方法的作用与解释。
3. join 方法异常处理
join 方法是 Thread 类中的方法,为了了解该方法的异常处理,我们先来简要的看下 join 方法的 JDK 源码:
public final void join() throws InterruptedException {        join(0);}代码块123
从源代码中我们可以看到, join 方法抛出了异常:
throws InterruptedException代码块1
所以,我们在使用 join 方法的时候,需要对 join 方法的调用进行 try catch 处理或者从方法级别进行异常的抛出。
try-catch 处理示例:
public class DemoTest implements Runnable{    @Override    public void run() {        System.out.println("线程:"+Thread.currentThread()+" 正在执行...");   }    public static void main(String[] args) {        Thread t1 = new Thread(new DemoTest());        t1. start();        try {            t1.join();       } catch (InterruptedException e) {            // 异常捕捉处理       }   }}
throws 异常处理示例:
public class DemoTest implements Runnable throws InterruptedException {    @Override    public void run() {...}    public static void main(String[] args) {        Thread t1 = new Thread(new DemoTest());        t1. start();        t1.join();   }}
4. join 方法如何使用
为了更好的了解 join 方法的使用,我们首先来设计一个使用的场景。场景设计:
线程 1 :执行时间 5 秒钟;
线程 2 :执行时间 10 秒钟;
线程 3 :执行 8 秒钟。
需求:我们需要等 3 个线程都执行完毕后,再进行后续代码的执行。3 个线程执行完毕后,请打印执行时间。
期望结果:10 秒执行时间。看到这个是不是似曾相识呢?这就是我们本节第 2 知识点所举出的示例,现在我们来进行代码实现和验证,体会 join 方法的使用。
实例:
public class DemoTest{    public static void main(String[] args) throws InterruptedException {        Thread threadOne = new Thread(new Runnable() { //线程 1            @Override            public void run() {                try {                    Thread.sleep (5000 ); //线程 1 休眠 5 秒钟               } catch (InterruptedException e) {                    e.printStackTrace();               }                System.out.println ("线程 1 休眠 5 秒钟,执行完毕。");           }       });        Thread threadTwo = new Thread(new Runnable() { //线程 2           ...                    Thread.sleep (10000 ); //线程 2 修眠 10 秒钟           ...                System.out.println ("线程 2 修眠 10 秒钟,执行完毕。");           }       });        Thread threadThree = new Thread(new Runnable() {//线程 3           ...                    Thread.sleep (8000 ); //线程 3 修眠 8 秒钟           ...                System.out.println ("线程 3 修眠 8 秒钟,执行完毕。");           }       });        Long startTime = System.currentTimeMillis();        threadOne. start();threadTwo. start();threadThree. start();        System.out.println("等待三个线程全部执行完毕再继续向下执行,我要使用 join 方法了。");        threadOne.join(); //线程 1 调用 join 方法        threadTwo.join(); //线程 2 调用 join 方法        threadThree.join(); //线程 3 调用 join 方法        Long endTime = System.currentTimeMillis();        System.out.println("三个线程都执行完毕了,共用时:"+ (endTime - startTime) + "毫秒");   }}
执行结果验证:
等待三个线程全部执行完毕再继续向下执行,我要使用 join 方法了。线程 1 休眠 5 秒钟,执行完毕。线程 3 修眠 8 秒钟,执行完毕。线程 2 修眠 10 秒钟,执行完毕。三个线程都执行完毕了,共用时: 10002毫秒代码块12345
从执行的结果来看,与我们对 join 方法的理解和分析完全相符,请同学也进行代码的编写和运行,加深学习印象。
5. 带参数的 join 方法使用
除了无参的 join 方法以外, Thread 类还提供了有参 join 方法如下:
public final synchronized void join(long millis)    throws InterruptedException代码块12
该方法的参数 long millis 代表的是毫秒时间。
方法作用描述:等待 millis 毫秒终止线程,假如这段时间内该线程还没执行完,也不会再继续等待。结合上一个知识点的代码,我们都是调用的无参 join 方法,现在对上一个知识点代码进行如下调整:
threadOne.join(); //线程 1 调用 join 方法threadTwo.join(3000); //线程 2 调用 join 方法threadThree.join(); //线程 3 调用 join 方法代码块123
从代码中我们看到,线程 2 使用 join 方法 3000 毫秒的等待时间,如果 3000 毫毛后,线程 2 还未执行完毕,那么主线程则放弃等待线程 2,只关心线程 1 和线程 3。
我们来看下执行结果:
等待三个线程全部执行完毕再继续向下执行,我要使用 join 方法了。线程 1 休眠 5 秒钟,执行完毕。线程 3 修眠 8 秒钟,执行完毕。三个线程都执行完毕了,共用时: 8000毫秒线程 2 修眠 10 秒钟,执行完毕。代码块12345
从执行结果来看, 总用时 8000 毫秒,因为线程 2 被放弃等待了,所以只考虑线程 1 和线程 3 的执行时间即可。
6. 小结
在实际的开发场景中,经常会设计到对 join 方法的使用,无参方法使用更加常见,了解 join 方法的使用非常重要。
本节重中之重,就是掌握 join 方法的使用,join 方法在后续的开发工作中非常关键,很多情况下都会有所使用。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
阿里一面:聊聊Java中线程的生命周期状态
Java并发编程:Thread类的使用
Java线程的5种状态及切换(透彻讲解)
多线程编程——基础篇 (一)
「并发编程」并发编程中你需要知道的基础概念
深入浅出:JAVA多线程编程实战-基础篇
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服