浅谈在Java代码中创建线程的多种方式

文章目录

  • 一、Thread 类
    • 1.1 跨平台性
  • 二、Thread 类里的常用方法
  • 三、创建线程的方法
    • 1、自定义一个类,继承Thread类,重写run方法
      • 1.1、调用 start() 方法与调用 run() 方法来创建线程,有什么区别?
      • 1.2、sleep()方法
    • 2、自定义一个类,实现Runnable接口,重写run方法
    • 3、继承 Thread 类,重写 run 方法,基于匿名内部类
    • 4、实现 Runnable 接口,重写 run 方法,基于匿名内部类
    • 5、匿名内部类
      • 5.1、回调函数的使用场景

一、Thread 类

线程本身就是OS(操作系统简称OS,以下统一使用OS表示操作系统)提供的概念,OS也提供了一些 API 供程序员使用,譬如说:Linux 提供 pthread ,而在Java中,就把OS提供的 API 进行了封装,统一使用 Thread 类,供程序员在Java代码中调用来创建/操作线程。

1.1 跨平台性

那可能有同学疑惑了,为啥Java要封装OS提供的操作线程的API,自己提供一个Thread类供Java程序员调用来操作线程呢??

这是因为Java语言的特性:跨平台。

只要是学习过Java语言的同学,肯定听说过:一次编译,终身运行 这句话吧。其实就是在描述Java语言的跨平台性。

那么Java语言如何实现其跨平台性呢??我简单描述一下吧,以便大家更深刻理解Java为啥要封装一个 Thread 类 供程序员调用,而不直接使用OS提供的API。

JVM 通过把不同OS提供的不同的API统一封装成相同风格的API给Java程序员使用,因此JVM就能够屏蔽不同的OS的差异。

此时Java程序员写程序代码,就不需要考虑当前写的这个程序是在哪个OS上运行,运行时是否适配此OS,因为这些问题已经由JVM解决了。

二、Thread 类里的常用方法

我们通过 Thread类 创建线程时,需要先了解 Thread 类中有哪些常用方法。
Java官方文档对Thread类里的方法介绍

三、创建线程的方法

1、自定义一个类,继承Thread类,重写run方法

第一种创建线程的方式就是:自定义一个类,并且使该类继承自Java标准库 Thread 类,此时自定义的类需要重写 run() 方法。

注意:重写的 run() 方法,要处理异常时,只能 try {} catch (),并不能 throws,这是因为 Thread 类中的 run() 方法并没有throws xxx这样的设定。

重写的 run() 方法里书写的逻辑代码就是我们创建出来的新线程,所要执行的任务。

自定义一个继承自 Thread 类的自定义类,并且在该类中重写 Thread 类里的 run()方法后,并没有真正创建出一个新线程,还需要调用 start() 方法,让它真正被创建出来并执行起来。

代码展示如下:

class MyThread extends Thread {
//    重写 run() 方法
    @Override
    public void run() {
        while (true) {
            System.out.println("hello world!");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class testThread1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
//       真正创建一个新的线程
        thread.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }

}

上述代码中,main() 方法中有一个 while循环,新线程的 run() 方法中也有一个 while 循环,这两个循环都是死循环。主线程(main)和新创建出的线程都在分别执行自己的循环。这两个线程都能参与到cpu的调度中,这两个线程是在并发执行,那么此时的运行结果就比较复杂不确定了,每台机子的性能都不一样,多个线程并发执行时,到底执行哪个线程,不知道,要看操作系统的调度。

我电脑的运行结果:
在这里插入图片描述

再来看看以下代码片段含有什么问题:

class MyThread extends Thread {
//    重写 run() 方法
    @Override
    public void run() {
        while (true) {
            System.out.println("hello world!");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class testThread1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
//       没有创建一个新的线程
        thread.run();
    }
}

运行结果:
在这里插入图片描述

上述代码片段作了一些改动:可以发现当前代码并没有调用 start() 方法去真正创建一个新的线程出来,而是调用了 run() ,但是其运行结果居然与调用 start() 方法一致,这是为什么呢??此处就涉及到一个问题:调用 start() 方法与调用 run() 方法来创建线程,有什么区别?

1.1、调用 start() 方法与调用 run() 方法来创建线程,有什么区别?

首先我们要明确:什么叫做创建出了新的线程,什么叫做没有创建出新的线程?

创建出了新的线程就是:每个线程都能够独立的调度执行。

当我们调用 start() 方法创建线程时,是真正的创建出了新的线程。此时的OS就会在底层调用创建线程的API,同时会在系统内核中创建出对应的PCB结构,并且将此PCB加入到对应的链表中。此时这个新创建出来的线程就会参与到cpu的调度中,执行任务。

run() 方法只是上面的入口方法,并没有去调动系统的API,在系统内核中创建出一个对应的PCB结构,因此并没有创建出新的线程。

以往我们只有一个线程,那就是main主线程,代码都是从前往后、从上到下执行的,遇到函数调用就先进入函数内部执行代码,然后再退出函数回到原来的代码段继续往后执行。但是现在我们接触了多线程的并发编程,虽然从宏观上来看,线程是同时在执行的,但其实多线程的执行顺序是不确定的,操作系统调度哪个线程到cpu上执行,就轮到哪个线程执行。每个线程,都是一个独立的执行流,每个线程都可以执行一段代码,多个线程之间是并发的关系。

1.2、sleep()方法

我们可以看到在代码中出现了sleep()方法,来了解以下这个常用方法。

sleep() 方法是 Thread 类的静态方法,线程调用该方法表示进入休眠/阻塞状态,其参数是 休眠的时间,单位是ms。

线程调用sleep()方法后,进入阻塞状态,当阻塞时间到,系统就会唤醒线程,并且恢复对线程的调度。如果是多个线程阻塞后都被唤醒了,那么此时谁先被调度到,谁后被调度,可以视为是”随机“的(随机在日常生活中,我们一般理解为是“概率均等”的情况,但是在这里只是看起来随机,因为我们也不知道操作系统是怎么调度的,我们只能在代码的设定上表示为随机),这样 “随机” 调度的过程,称为 “抢占式执行”

2、自定义一个类,实现Runnable接口,重写run方法

class MyRunnable implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("hello world!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class TestThread2{
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
        while (true) {
            System.out.println("hello main!");
            Thread.sleep(1000);
        }
    }
}

这类创建线程的方法,把线程本身与线程要执行的任务(在 Runnable中)分开了,进一步的解耦合了。当我们还想要并发编程,但是不想使用线程的方式实现并发编程,想使用其他方式时:譬如说线程池、协程…就可以使用Runnable搭配他们来使用,进而实现并发编程。

第一种方法创建线程,是将线程本身与线程所要执行的任务放在了一起,耦合度较高。

3、继承 Thread 类,重写 run 方法,基于匿名内部类

/**
 * 继承自Thread,重写run()方法,基于匿名内部类
 */
public class testThread3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(){
            @Override
            public void run() {
                while (true){
                    System.out.println("hello world!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
        while (true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

4、实现 Runnable 接口,重写 run 方法,基于匿名内部类

/**
 * 实现 runnable ,重写 run(),基于匿名内部类
 */
public class testThread4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable(){
            public void run(){
                while (true){
                    System.out.println("hello world!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        t.start();
        while (true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

5、匿名内部类

上述的几种创建线程的方法,多多少少有些啰嗦、复杂。因此就有了第5种方法:使用匿名内部类。

匿名内部类是一个 lambda 表达式,这个 lambda 表达式里就表示了run()方法里的内容。

lambda 表达式,本质上是一个 匿名函数,这样的匿名函数,主要可以用来作为回调函数来使用。

回调函数:先写好,但不需要程序员手动调用,在合适的时机自动被调用。

5.1、回调函数的使用场景

1、服务器开发:服务器收到一个请求,就会触发一个对应的回调函数,使用回调函数对该请求做出具体的处理。
2、图形界面开发:针对用户的某个操作,触发一个对应的回调。

/**
 * 使用 匿名函数 创建线程
 */
public class testThread5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (true){
                System.out.println("hello world!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        while (true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

在这里插入图片描述

那么在我们上述的代码中,此回调函数什么时候执行呢??即当线程被真正创建出来时会自动执行。

创建线程的方法还有许多许多,主要是介绍常用的写法,还有的写法后续会继续补充!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式常见存储器

阅读引言: 在看一款芯片的数据手册的时候, 无意间翻到了它的启动模式(Boot Mode), 发现这种这么多种ROM,所以就写下了这篇文章。 目录 一、存储器汇总 二、易失性存储器(RAM) 1. SRAM 1.1 单口SRAM 1.2 双口SRAM 2. DRAM 2.1 SDRAM 2…

Fast-DetectGPT 无需训练的快速文本检测

本文提出了一种新的文本检测方法 ——Fast-DetectGPT,无需训练,直接使用开源小语言模型检测各种大语言模型,如GPT等生成的文本内容。 Fast-DetectGPT 将检测速度提高了 340 倍,将检测准确率相对提升了 75%,超过商用系…

有哪些好用电脑端时间定时软件?桌面日程安排软件推荐 桌面备忘录

随着现代生活节奏的加快,人们对于时间管理和任务提醒的需求越来越大。为了满足这一需求,市场上涌现出了众多桌面便签备忘录软件,它们不仅可以帮助我们记录待办事项,还能定时提醒我们完成任务。在这篇文章中,我将为大家…

计算机研究生如何在顶级会议了解行业方向

以为例子论文可视化 |WACV 2022 年 (thecvf.com)https://wacv2022.thecvf.com/papers-visualizations?filterprimary_subject_area&search3DComputerVision 这些图表适用于IEEE/CVF 计算机视觉冬季会议 (WACV) 2022。顶部图表是根据彼此相似性分布的会议主要会议论文的可…

微电子领域材料生长方法(六)液相外延(LPE)

微电子领域材料生长方法(六)液相外延(LPE) 液相外延(Liquid Phase Epitaxy, LPE)是一种用于生长单晶薄膜的技术,特别是在半导体材料的制备中。LPE技术允许在较低的温度下从熔体中生长出高质量的…

Visual 下载 NuGet包速度变慢

Visual 下载 NuGet包速度变慢 最近遇到一个问题,即我在使用 Visual Studio 下载 Nuget 包的时候会发现变得特别慢,那么该如何解决该问题呢 Visual Studio → 工具 → NuGet 包管理项 → 程序包管理设置 → 程序包源 从上面我们可以看到我使用的包源地址…

2024 最新免费听全网音乐神器

之前分享过几个的音乐软件挂了2024最新神器app,全网音乐免费听 ,这里再整理分享下,下载地址 https://pan.quark.cn/s/b52ada313fbd 玩转互联网达人 苏生不惑备用号,分享各种黑科技软件资源和技巧,带你玩转互联网。 …

12.JAVAEE之网络原理2

1.网络层 网络层要做的事情,主要是两方面, 1)地址管理,制定一系列的规则,通过地址,描述出网络上一个设备的位置. 2)路由选择.网络环境比较复杂的,从一个节点到另一个节点之间,存在很多条不同的路径,就需要通过这种方式,筛选/规划出更合适的路径进行数据传输 IP协议 8位协议&…

HackMyVM-Convert

目录 信息收集 arp nmap WEB web信息收集 gobuster RCE漏洞 反弹shell 提权 get user.txt 提权 信息收集 arp ┌──(root㉿0x00)-[~/HackMyVM] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 08:00:27:77:ed:84, IPv4: 192.168.9.126 Starting…

律师口才训练技巧课程介绍?

律师口才训练技巧课程介绍 一、课程背景与目标 律师口才作为法律职业的核心能力之一,对于律师在**辩论、法律咨询、谈判协商等场合的表现具有至关重要的作用。然而,许多律师在口才方面存在不足,难以充分发挥自己的专业能力。因此,…

CTF之eval

首先我们先了解一下eval()函数 什么是eval()? eval() 函数把字符串按照 PHP 代码来计算。 该字符串必须是合法的 PHP 代码,且必须以分号结尾。 如果没有在代码字符串中调用 return 语句,则返回 NULL。如果代码中存在解析错误…

数据结构——二叉树的顺序存储(堆)(C++实现)

数据结构——二叉树的顺序存储(堆)(C实现) 二叉树可以顺序存储的前提堆的定义堆的分类大根堆小根堆 整体结构把握两种调整算法向上调整算法递归版本 非递归版本向下调整算法非递归版本 向上调整算法和向下调整算法的比较 我们接着…

【Linux系统化学习】生产者消费者模型(阻塞队列和环形队列)

目录 生产者消费者模型 什么是生产者消费者模型 为什么要使用生产者消费者模型 生产者消费者模型的优点 为什么生产者和生产者要互斥? 为什么消费者和消费者要互斥? 为什么生产者和消费者既是互斥又是同步? 基于BlockingQueue的生产者…

将数组中最大的数放在最后一位,最小的数放在第一位

#include <stdio.h> int main() {void input(int number[]);void output(int number[]);void swapmaxmin(int number[]);int number[10];input(number);//swapmaxmin(number);output(number);return 0; }//往一个数组里输入 void input(int number[]) {int i;for(i0;i<…

Bert类模型也具备指令遵循能力吗?

深度学习自然语言处理 原创作者&#xff1a;Winnie BERT模型&#xff0c;依托Transformer架构及其大规模预训练&#xff0c;为自然语言处理领域带来了深远的影响。BERT模型架构包含多层双向Transformer编码器&#xff0c;通过这种结构&#xff0c;BERT及其家族成员&#xff0c;…

tensorflow_decision_forests\tensorflow\ops\inference\inference.so not found

恰好有一个帖子提到了py3.10里面的解决方案 pip install --user tensorflow2.11.0My tensorflow version is 2.11.0 and my tensorflow_decision_forests version is 1.2.0 so those should be compatible. I also am using Python version 3.10.11原文链接&#xff1a; http…

R语言高级数据管理

一&#xff0c;数学函数 绝对值函数abs(x) sqrt(x) 开平方根 不小于某个数的最小整数ceiling(x) 不大于某个数的最大整数floor(x) 四舍五入round(x) sin(x) cos(x) log(x) 二&#xff0c;统计函数 求平均值 > x<-c(2,3,4,5,6,7,8,9,10) > mean(x) 求和 &g…

Entity Framework6 Oracle 官网开发说明

Entity Framework, LINQ and Model-First for the Oracle Database

SAP-ERP TM运输管理模块详解-1

简介 SAP中的运输功能(即TM模块,属于SD的子模块)是后勤执行的一部分,用于自动计算交货成本。也就是说,SAP可以让系统自动对销售发货的商品计算运费,对于运费占这个成本很大比重的销售模式,可以使用该功能。运输功能相对于SD其他模块,相对比较独立的,应用面不是很广。其…

01.JAVAEE初阶之计算机如何工作

1.一台机器如何组成 冯诺依曼体系 CPU 中央处理器: 进行算术运算和逻辑判断.存储器: 分为外存和内存, 用于存储数据(使用二进制方式存储)输入设备: 用户给计算机发号施令的设备.输出设备: 计算机个用户汇报结果的设备. 针对存储空间 硬盘 > 内存 >> CPU针对数据访问…