Java多线程初阶(一)(图片+源码+超详细)

news2024/11/16 1:51:14

线程的概念参照以往的这篇文章🐻

目录

 1.创建线程

1.1 继承Thread类

1.2 实现Runnable接口

 eg:常用的简写方式

2.Thread类中的常用API

3. start方法和run方法

4. 继承Thread类启动新线程的逻辑

5. 实现Runnable接口启动新线程的逻辑 

6. 线程相关API

6.1 中断一个线程(包含注意事项)

6.2 等待一个线程 - join

6.3 获取当前线程的引用

6.3 休眠当前线程


 1.创建线程

1.1 继承Thread类

😄创建并启动线程的第一种方法是让我们的自定义类继承java.lang.Thread类,覆写其中的run方法,然后调用Thread类的start方法即可启动线程。

这里为什么调用的是start方法而不是run方法我们再接下来分析源码后试着理解下,现在先来看下新线程的创建和启动。

 在实际的开发过程中,由于Java单继承机制,我们很少直接使用继承Thread的方法创建和启动线程,接下来看下第二种创建线程的方式。

1.2 实现Runnable接口

这是实现多线程的第二种方法,让我们的自定义线程类实现Runnable并且实现接口中唯一的一个run方法,然后将创建Thread对象,将我们的Runnable实例对象通过构造方法传入,接着调用Thread类的start方法即可启动线程。这个过程如下图所示:

这种创建新线程的方式解决了单继承的缺陷,同时降低了程序的耦合度,是一种在开发中经常倍使用的创建新线程的方式。 

 eg:常用的简写方式

在创建新线程时,通常会使用匿名内部类或者lambda表达式的形式来简化书写。例如:

  • 使用匿名内部类的方式创建新线程
  • 使用lambda表达式的方式创建新线程

以下是使用这几种方式创建新线程的写法(备忘):

public class Main {
    public static void main(String[] args) {
        //使用匿名内部类继承Thread类实现多线程
        Thread threadAnonymous = new Thread("threadAnonymous-0") {
            @Override
            public void run() {
                System.out.println("这是使用匿名内部类继承Thread类启动的新线程" + this.getName());
            }
        };
        //使用匿名内部类实现Runnable接口创建的新线程
        Thread threadImplIter = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是使用匿名内部类实现Runnable接口启动的新线程" + Thread.currentThread().getName());
            }
        }, "threadImpIter-1");

        //使用lambda表达式实现的多线程
        Thread threadLambda = new Thread(() -> {
            //直接书写run方法的逻辑
            System.out.println("这是使用lambda启动的新线程" + Thread.currentThread().getName());
        },"threadLambda-2");
        threadAnonymous.start();
        threadImplIter.start();
        threadLambda.start();
    }
}

2.Thread类中的常用API

Thread类常用的构造方法:

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());    //传入Runnable接口实例
Thread t3 = new Thread("这是我的名字");      //指定新创建的线程的名字
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");    //传入Runnable接口实例,并指定新线程的名字
Thread类中的常用的获取属性的方法:
属性方法
线程 IDgetId()
线程名称getName()
线程状态getState()
线程优先级getPriority()
是否后台进程isDaemon()
是否存活isAlive()
是否被中断isInterrupt()

😄关于上表中线程的一些属性说明:

  • 优先级(priority)高的线程理论上来说更容易被调度到,但是实际情况是不能确定的
  • 关于线程是否存活,简单来说就是run方法是否执行结束
  • 关于后台进程,就是在所有的非后台进程执行结束后,这个后台进行就会强制被结束

3. start方法和run方法

😄现在我们来理解为什么新线程的执行逻辑是我们覆写的run方法,而启动新线程却要调用start方法来启动❓

1.调用被覆写的 run方法只是单纯的调用这个方法,并不会启动新线程,我们可以通过JDK自带的调试工具jconsole查看我们程序中正在运行的线程 :

2.调用start方法的本质是调用Thread类中的start0方法,这个start0方法在底层会执行一系列操作来启动我们创建的线程并且让这个新线程调用我们覆写的run方法。 

😄所以,在我们启动新线程时一定是调用Thread类的start方法去真正启动一个新线程然后让这个新线程调用我们的run方法;而不是简单的调用我们的run方法❗❗❗

4. 继承Thread类启动新线程的逻辑

😄我们编写的自定义类继承Thrad类并重写其中的run方法后,在去调用Thread类的start方法时,其中的start方法调用start0方法,而start0方法启动的新线程调用Thrad类中的run方法时,会动态绑定到我们自定义类中覆写的run方法,也就执行到了我们自己书写的run方法逻辑。

5. 实现Runnable接口启动新线程的逻辑 

😄Runnable接口中只有一个run方法,那么它是怎么实现我们线程的创建和启动的呢?再来回顾实现Runnable接口创建和启动线程的逻辑过程:

1. 我们在实现Runnable接口后是将它的实例当作构造参数传入了Thread对象的构造中,这个Runnable接口实例对象会被赋值给Thread对象中的成员变量身上。

public
class Thread implements Runnable {
    ...
   /* What will be run. */
    private Runnable target;    //Runnable接口的实例对象传进来后被赋值给这个成员属性
    ...
}

2. 当我们调用Thread对象的start方法时,start方法调用start0方法,底层start0执行创建出新线程对象时调用我们的run方法,而Thread类中的run方法回去调用target成员属性的run方法,我们的实例对象又实现了Runnable接口,此时发生动态绑定就调用到了我们覆写的run方法。其最终效果和直接执行Thread类创建和启动新线程的效果还是一样的。有的绕的话我们画张图来描述下这个过程:

6. 线程相关API

6.1 中断一个线程(包含注意事项)

😄当一个线程进入到工作状态后,按理来说应当是执行完自己的逻辑后才停止运行。但是这种一成不变的执行方法在实际开发中可能并不适用。因此我们在想有没有一种方法能够让其接受我们的指控,在某些特定的时间终止线程的运行呢?

我们可以通过volatile修饰的自定义标志位的方法,让线程在某些时刻停止运行。同时在Thread内部已经为我们提供了一个boolean类型的变量作为线程是否被中断的标志,我们可以直接借用这个标志位变量控制我们线程的的中断。下面是相关操作的API:

方法说明
public void interrupt()中断对象关联的线程,如果线程处于阻塞状态,则会抛出阻断异常,否则设置该线程的阻断标志位为false
public static boolean Interrupted()判断当前线程的中断标志位是否被设置,且在调用后清除标志位
public boolean isInterrupted判断对象关联线程的标志位是否设置,调用后不清楚标志位

既然只是标志位,那么我们应当能够想到单纯的调用一个线程的interrupt方法只是改变了这个标志位的值,并不能真正阻断这个线程的执行。

  • 因此interrupt方法通常与实例方法isInterrupted搭配使用,当线程的标志位状态被实例的interrupt方法设置后,这个线程在获取自己标志位状态不满足条件后就终止运行。这里应当注意的是线程的中断标志位默认是false。以下是程序实例:
public class InterruptTestDrive {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //在循环的条件判断中新增了获取当前线程中断标志位的状态,但是不要修改标志位的值
                //如果当前线程的标志位的值为false,即没有被设置,则继续执行。否则因不满足循环条件退出循环
                System.out.println(Thread.currentThread().isInterrupted());
                for (int i = 0; i < 1_0000_0000 && !Thread.currentThread().isInterrupted(); i++) {
                    System.out.print(i + " ");
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.interrupt();
        System.out.println(thread.isInterrupted());
    }
}
  • 同时当通过interrupt改变线程中断标志位的值时,如果当前被改变线程由于wait/join/sleep方法而被阻塞挂起,则会抛出InterruptedException异常并清除标志位(将标志位恢复到默认状态)。
public class InterruptTestDrive {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().isInterrupted());
                for (int i = 0; i < 1_0000_0000; i++) {
                    System.out.print(i + " ");
                    try {
                        //当前这个线程绝大部分时间处于阻塞状态,当我们调用当前线程对象的interrupt方法时会抛出InterruptedException异常,
                        // 我们捕获并处理这个异常来决定这个线程的执行状态,同时这时线程对象调用的interrupted方法会恢复中断标志位的默认值false
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        //在这里处理中断异常
                        break;
                    }
                }
            }
        });
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
        System.out.println(thread.isInterrupted());     //thread的中断标志位并没有被设置为true,仍然保持着默认值
    }
}

那么,实例的isInterrupted方法和静态的interrupted方法有什么区别呢?

  • 实例的isInterrupted会判断与Thread对象关联的线程的中断标志位是否被设置,并不会清除中断标志位的值。
  • 静态的interrupted方法,是判断当前线程的中断标志位是否被设置,是先获取再清除当前线程的中断标志位。即当前线程的中断标志位会先进行保存,然后恢复中断标志位的默认值false,返回保存的标志位的值

6.2 等待一个线程 - join

😄有时,我们需要在当前线程中等待其他线程执行结束后,才能进行下一步工作,这是我们就可以使用等待线程对象的join方法。Thread类中有三种重载重载的join方法,如下所示:

方法说明
public void join()等待线程结束,可以认为是死等(不见不散😂)
public void join(long millis)等待线程结束,最多等millis毫秒。等不到就继续当前线程的执行
public void join(long millils,int nanos)同一个参数的join方法,只不过精度更高了

例如,我们需要在main线程中等待一个自定义线程类执行再接着执行main线程中接下来的逻辑。以下是程序实例代码:

public class JoinThreadTestDrive {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            //lambda表达式。直接书写run方法的逻辑
            for (int i = 0; i < 5; i++) {
                System.out.println(i + 1 + "thread-0");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "thread-0");
        thread.start();
        System.out.println(1 + "-main");
        System.out.println(2 + "-main");
        System.out.println("main线程开始等待thread-0线程的执行");
        
        
        //main线程死等thread-0线程,直到thread-0线程执行结束后才接着往下执行
        //thread.join();      
        
        
        //main线程等待thread-0线程1666ms,在0-1666ms之间thread-0线程执行结束了,main线程就直接往下执行
        //否则,main线程最多等待thread线程1666ms,如果1666ms之后thread-0线程仍未执行结束,则直接往下执行,不再等待thread-0线程
        //main:设还没点小脾气呢🙂!
        thread.join(1666);
        //在当前main线程中等待thread-0线程执行结束后再执行接下来的逻辑
        System.out.println("main线程等待结束");
        System.out.println(3 + "-main");
    }
}

6.3 获取当前线程的引用

方法说明
public static Thread currentThread();返回当前线程对象的引用

6.3 休眠当前线程

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程millis毫秒
public static void sleep(long millis,int nanos) throws InterruptedException可以更高精度的休眠

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/446822.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux基础—日志分析

Linux基础—日志分析 一、日志的功能1.日志消息的级别2.设备字段说明 二、日志文件的分类1.内核及系统日志2.用户日志3.程序日志 三、日志文件1.日志文件查看2.主要日志文件介绍3.日志管理策略 一、日志的功能 用于记录系统、程序运行中发生的各种事件 通过阅读日志&#xff0c…

图像描述算法排位赛:SceneXplain与MiniGPT-4谁将夺得桂冠?

如果你对 AI 前沿感兴趣&#xff0c;本场「图像描述算法排位赛」绝对是你不能错过的&#xff01;在这场较量中&#xff0c;SceneXplain 和 MiniGPT-4 将会比试&#xff0c;谁将摘得这场比赛的桂冠&#xff1f; &#x1f4ce; 直接上手体验&#xff1a;scenex.jina.ai 背景介绍 …

手机存储数据恢复软件哪个好用?试了10款,我只认准这一款!

案例&#xff1a;手机存储数据恢复软件哪个好用&#xff1f; 【有没有好介绍的苹果数据恢复软件&#xff1f;可以恢复好几年数据的那种软件&#xff1f;求推荐&#xff01;】 手机中的数据是用户平时使用手机时不可避免的内容&#xff0c;这些数据包括照片、视频、音乐、文件等…

Baumer工业相机堡盟工业相机如何通过BGAPISDK的软触发实现两相机同步采集(C++)

Baumer工业相机堡盟工业相机如何通过BGAPISDK的软触发实现两相机的同步采集&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的高速同步采集的技术背景Baumer工业相机通过BGAPI SDK在回调函数里同步保存图像工业相机在回调函数BufferEvent保存工业相机使用软触发进行同…

【观察】华为:新一代楼宇网络,使能绿建智慧化

“碳达峰”、“碳中和”目标是我国生态文明建设和高质量可持续发展的重要战略安排&#xff0c;将推动全社会加速向绿色低碳转型。作为全球既有建筑和每年新建建筑量最大的国家&#xff0c;大力发展绿色建筑对中国全方位迈向低碳社会、实现高质量发展具有重要意义。 《“十四五”…

使用FirmAE 对zyxel路由器固件仿真实践 | 信息安全

一、FirmAE简介 FirmAE 是一个执行仿真和漏洞分析的全自动框架。FirmAE 使用五种仲裁技术显著提高仿真成功率&#xff08;从Firmadyne的 16.28% 提高到 79.36%&#xff09;。 FirmAE的整体架构为如上图所示。与Firmadyne类似&#xff0c;FirmAE在预先构建的自定义Linux内核和库…

Codeforces Round 816 (Div. 2)(C. Monoblock)

传送门 题意&#xff1a; 定义一个数组&#xff0c;定义它 相等连续段的个数 如1,2,2,1&#xff0c;相等连续段有[1],[2,2],[1]&#xff0c;共3个 如2,2,2,1&#xff0c;相等连续段有[2,2,2],[1]&#xff0c;共2个 给定一个数组a&#xff0c;定义g(l,r)为&#xff0c;子数组中…

fisco bcos 2.0+ 版本浏览器搭建

Fisco Bcos当前已经发布了3版本&#xff0c;在3版本中&#xff0c;Fisco摒弃了原有的区块链浏览器&#xff0c;而是提供了具有更为丰富功能的图形化区块链管理工具WeBase&#xff0c;后续也会提供对于WeBase的搭建部署教程。 文章目录 一、准备条件二、下载浏览器安装脚本1. 下…

ChatGPT4 的体验 一站式 AI工具箱 -—Poe(使用教程)

最近由于人工智能聊天机器人的爆火(ChatGPT)&#xff0c;因此各种各样的AI助手流行与网络&#xff0c;各种各样的都有&#xff0c;不论是什么样的其实都是为了我们更方便的解决问题&#xff0c;今天介绍一款AI工具箱——Poe将多种AI集成与一个界面&#xff0c;大家可以一次感受…

Mac系统brew报错“The GitHub credentials in the macOS keychain may be invalid”解决

报错信息如下&#xff1a; $ brew search nginx Warning: Error searching on GitHub: GitHub API Error: Requires authentication The GitHub credentials in the macOS keychain may be invalid. Clear them with: printf "protocolhttps\nhostgithub.com\n" | …

Python双向链表的操作

目录 一、双向链表 双向链表示例图 二、双向链表的操作 1、判断链表是否为空 2&#xff0c;链表长度 3&#xff0c;遍历整个链表 4&#xff0c;在链表头部添加元素 5、链表尾部添加元素 6&#xff0c;在指定位置插入元素 7&#xff0c;修改指定位置的元素 8&#xff0…

百度ai智能写作工具-百度ai自动写文章

百度AI智能写作工具&#xff1a;让创作更快捷、高效&#xff01; 在当今竞争激烈的文化创意市场中&#xff0c;创作一篇高质量的文章需要投入大量时间和精力。然而&#xff0c;有了百度AI智能写作工具&#xff0c;创作变得更快捷、高效了。 百度AI智能写作工具采用最先进的人…

JVM之垃圾回收器概述

目录 垃圾收集器分类 按线程数分 按照工作模式分 ​编辑 按碎片处理方式分 按工作的内存区间分 评估GC的性能指标 吞吐量 暂停时间 吞吐量 vs 暂停时间 垃圾回收器概述 垃圾收集器没有在规范中进行过多的规定&#xff0c;可以由不同的厂商、不同版本的JVM来实现。 由…

一起学 WebGL:感受三维世界之视图矩阵

大家好&#xff0c;我是前端西瓜哥。之前绘制的图形都是在 XY 轴所在的平面上&#xff0c;这次我们来加入一点深度信息 z&#xff0c;带你走入三维的世界。 视图矩阵 对于一个立方体来说&#xff0c;我们从它的正前方看&#xff0c;不管距离它多远&#xff0c;也只能看到一个…

微服务下网关聚合Swagger文档、starter统一配置Swagger

一、starter实现统一配置微服务文档 把Swagger配置中的公共部分抽取出来Swagger与SpringBoot整合中&#xff0c;可能会由于版本问题出现各种问题 1、制作starter 参考&#xff1a; 【SpringBoot】自定义启动器 Starter【保姆级教程】用starter实现Oauth2中资源服务的统一配置用…

中级软件设计师备考---数据库系统1

目录 数据库模式数据库的设计过程E-R模型关系代数与元组演算 数据库模式 三级模式、两级映射 定义&#xff1a; 三级模式&#xff1a;外模式、概念模式和内模式&#xff1b;两级映射&#xff1a;外模式-概念模式映射、概念模式-内模式映射 外模式是用户看到的数据库的部分 概…

Linux的常见指令 -掌握

前言 为什么要学命令行&#xff1f; windows/苹果图形界面&#xff0c;是商业化的产物&#xff0c;也就是使用必须简单小白&#xff0c;才能有人用&#xff0c;so what&#xff1f;严格意义上讲&#xff0c;我们必须要学一下Linux命令行。因为企业后端有大量的服务器&#xff…

Web3.0:重新定义互联网的未来

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Web3.0&#xff1a;重新定义互联网的未来 Web3.0是指下一代互联网&#xff0c;也称为“分布式互联网”。相比于Web1.0和Web2.0&#xff0c;Web3.0具有更强的去中心化、…

如何快速查找下载外文文献,哪个文献下载网站好用

​​如何高效获取到自己需要的外文文献&#xff0c;最好的办法就是去文献来源数据库中查找&#xff0c;你需要的文献来源数据库有可能是Elsevier&#xff08;sciencedirect&#xff09;、也可能是Wiley Online Library、也有可能是IEEE等等&#xff0c;外文数据库机构太多了。这…

微信跨平台方案Donut快速上手

一、Donut简介 Donut 是微信开发出的多端框架&#xff0c;用于支持使用小程序原生语法开发移动应用的框架&#xff0c;开发者可以一次编码&#xff0c;就可以编译出小程序和 Android 以及 iOS 应用&#xff0c;实现多端开发。能够帮助企业有效降低多端应用开发的技术门槛和研发…