创建线程的4种方法

news2024/11/15 18:01:31

目录

一.前言

1.关于进程调度

(1)为什么要调度?

(2)调度的真正对象

(3)调度的资源

2.线程 

(1).线程的写法

 (2)线程创建的方法

1.继承Thread

(1)使用继承Thread,重写run的方式来创建线程

(2)继承Thread,使用匿名内部类

2.实现Runnable

(1)使用实现Runnable,重写run

(2)实现Runnable,使用匿名内部类

3.基于lambda表达式

4.实现callable

三.优缺点总结

继承Thread类

实现Runnable接口

基于Lambda表达式

实现Callable接口

四.总结体会


一.前言

1.关于进程调度

(1)为什么要调度?

通俗来说,就是狼多肉少.

计算机中的CPU,内存等资源都是很有限的.

(2)调度的真正对象

CPU是按照并发的方式来执行进程的

引入进程,就是为了能够实现多个任务并发执行这样的效果

进程有个重大的问题就是比较重量,如果频繁的创建/销毁进程,成本会比较高

进程里面包括线程,一个进程里可以有一个线程,或者多个线程
每个线程都是一个独立的执行流.多个线程之间,也是并发执行的

多个线程可能是在多个 CPU 核心上, 同时运行
也可能是在一个 CPU 核心上, 通过快速调度,进行运行

 操作系统,真正调度的,是在调度线程,而不是进程

线程是 提作系统 调度运行 的基本单位
进程是 操作系统 资源分配 的基本单位

前面所说的进程调度,指的是这些进程里面只有一个线程

(3)调度的资源

当我们创建了一个进程之后,操作系统会创建一个PCB,把这个PCB加入到链表上

PCB中提供了一些属性,进程的优先级,进程的状态,进程的上下文,进程的记账信息...

一个进程中的多个线程之间,共用同一份系统资源

1)内存空间

2) 文件描述符表

只有在进程启动,创建第一个线程的时候,需要花成本去申请系统资源一旦进程(第一个线程)创建完毕,此时,后续再创建的线程,就不必再申请资源了,创建/销毁 的效率就提高了不少了.

既然线程的效率这么高,那是不是线程越多越好呢?

当然不是

CPU的核心数是固定的,此时创建出大量线程,没法立即被处理的线程就只能阻塞等待,就算此时强行进行调度,调度上了一个线程,那也势必会挤掉其它线程,总并发程度仍然是固定的.

真正有效果的是,再搞几个CPU,也就是再搞一个主机,这也就是我们所说的分布式系统

关于分布式系统,详情可见我的另一篇文章http://t.csdn.cn/DdQHj

由于线程就是进程的一部分,因此,如果一个线程出现异常,那么很有可能其它线程也会不能运行.

因此,我们要能够明确区分进程和线程之间的区别:

  • 进程包含线程
  • 进程有自己独立的内存空间和文件描述符表.同一个进程中的多个线程之间,共享同一份地址空间和文件描述符表
  • 进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位.
  • 进程之间具有独立性,一个进程挂了,不会影响到别的进程:同一个进程里的多个线程之间,一个线程挂了,可能会把整个进程带走,影响到其他线程的

那么Java怎么进行多线程编程呢?

首先,大家可能会有一个疑问,为什么Java不学习多进程编程呢?

虽然Java里提供了一组多进程编程的API,但是JDK里面没有封装这些多进程的API,因此Java里不提倡多进程编程.

2.线程 

接下来,我们就来具体学习多线程编程

(1).线程的写法

我们先来了解一下线程. 

Java标准库里提供了一个类Thread能够表示一个线程.

package thread;

class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("Hello Thread");
    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t=new MyThread();
        t.start();
    }
}

我们来分析这段代码

上述代码涉及到两个线程

1.main方法所对应的线程(一个进程里面至少有一个线程),也可以称为主线程

2.通过t.start创建新的线程

我们现在对代码进行调整,具体体会一下,"每一个线程是一个独立的执行流"

代码如下:

package thread;

class MyThread extends Thread{
    @Override
    public void run(){
        while(true){
            System.out.println("Hello Thread");
        }
    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t=new MyThread();
        t.start();
        while(true){
            System.out.println("hello main");
        }
    }

}

 

 运行结果如下

我们可以看到,hello Thread 和hello main都能打印出来

run()方法

run叫做入口方法,不是构造方法

run方法不是我们随便写的一个方法,是重写了父类的方法

这种重写一般是功能的扩展,一般这样的重写方法不需要我们自己手动调用,已经有其它代码来调用

run方法可以成为是一个特殊的方法,也就是线程的入口方法

而start方法,是调用操作系统中的api,创建新线程,新的线程里面调用run方法

 (2)线程创建的方法

线程创建主要有以下几种方法:

1.继承Thread

2.实现Runnable

3.基于lambda

4.实现callable

接下来,我们来详细介绍这几类方法

1.继承Thread
(1)使用继承Thread,重写run的方式来创建线程
class MyThread extends Thread{
    @Override
    public void run() {
        while(true){
            System.out.println("Hello t");
            }
        }
    }
    
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t=new MyThread();
        //start会创建新的线程
        t.start();
        //run不会创建新的线程,run是在main的线程中执行的
        while(true){
            System.out.println("Hello main");
        }
    }
}

运行结果如下: 

同时打印的原理是由于两个线程在同时执行,并且每个线程都有自己的输出流。在这段代码中,主线程和MyThread线程都在执行无限循环,分别打印"Hello main"和"Hello t"。

当两个线程同时执行时,它们会竞争CPU的资源,操作系统会根据调度算法来决定哪个线程获得CPU的执行权。由于线程的执行速度非常快,所以看起来就像是同时执行。

那这里的hello main的打印和hello t的打印有什么规律吗?

实际上是没有的,这是由于调度的随机性

当两个线程同时执行时,它们会竞争CPU的资源,操作系统会根据调度算法来决定哪个线程获得CPU的执行权。由于线程的执行速度非常快,所以看起来就像是同时执行。

每个线程都有自己的输出流,所以它们可以独立地打印输出。

当主线程执行System.out.println("Hello main")时,它会将输出发送到主线程的输出流中。而MyThread线程执行System.out.println("Hello t")时,它会将输出发送到MyThread线程的输出流中。

由于输出流是独立的,所以两个线程的输出可以同时显示在控制台上。但是由于输出的速度和顺序是不确定的,所以可能会出现交错的情况,即"Hello t"和"Hello main"的输出顺序可能会不一致。

(2)继承Thread,使用匿名内部类
public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t=new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("Hello t");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {//sleep睡眠过程中就将其打断
                        // throw new RuntimeException(e);
                        e.printStackTrace();
                    }
                }
            }
        };

        t.start();
        while(true){
            System.out.println("Hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {//sleep睡眠过程中就将其打断
                // throw new RuntimeException(e);
                e.printStackTrace();
            }
        }
    }
}

运行结果如下:

2.实现Runnable
(1)使用实现Runnable,重写run
class MyRunnable implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("Hello t");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {//sleep睡眠过程中就将其打断
               // throw new RuntimeException(e);
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        MyRunnable runnable=new MyRunnable();
        Thread t=new Thread(runnable);
        t.start();

        while(true){
            System.out.println("Hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {//sleep睡眠过程中就将其打断
                // throw new RuntimeException(e);
                e.printStackTrace();
            }
        }
    }

}

我们可以看到打印的结果如下: 

Runnable的字面意思是可运行的,使用Runnable 来描述一个具体的任务

第一种写法是使用Thread的run来描述线程入口

这一种是使用Runnable interface 描述线程入口

这两种方法之间并没有本质区别,只是使用方法的不同

(2)实现Runnable,使用匿名内部类
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("Hello t");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {//sleep睡眠过程中就将其打断
                        // throw new RuntimeException(e);
                        e.printStackTrace();
                    }
                }
            }
        });

        t.start();
        while(true) {
            System.out.println("Hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {//sleep睡眠过程中就将其打断
                // throw new RuntimeException(e);
                e.printStackTrace();
            }
        }
    }
}

运行结果如下: 

3.基于lambda表达式

这个方法是创建线程最推荐的写法,使用lambda表达式,这也是最简单直观的写法.

在Java里面,函数(方法)是无法脱离类的,但是lambda就相当于一个例外,所以这样的函数一般都是一次性的,用完就会被销毁.

lambda表达式的基本写法

()->{


}

()里面放参数,如果只有一个参数,可以省略() 

{}里面存放函数体,如果这里面只有一行代码,也可以省略{}

举一个代码例子:

public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t=new Thread(() -> {
            while(true){
                System.out.println("Hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {//sleep睡眠过程中就将其打断
                    // throw new RuntimeException(e);
                    e.printStackTrace();
                }
            }
        });


        t.start();
        while(true) {
            System.out.println("Hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {//sleep睡眠过程中就将其打断
                // throw new RuntimeException(e);
                e.printStackTrace();
            }
        }
    }
}

运行结果如下:

4.实现callable

Callable的用法非常类似于Run

使用Runnable写出的代码描述了一个任务,也就是一个线程要做什么.

然而,Runnable通过run方法描述,返回类型是void.但是很多时候,我们是希望任务有返回值的.二+而callable的call方法就是由返回值的.

比如说,我们写个代码,创建一个线程,用这个来计算1+2+...+1000.

我们来看具体的代码:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class ThreadDemo27 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        Callable<Integer> callable = new Callable<Integer>() {
            int sum = 0;

            @Override
            public Integer call() throws Exception {
                for (int i = 1; i <= 1000; i++) {
                    sum += i;
                }
                return sum;
            }
        };

        //找一个线程完成这个任务
        //Thread不能直接传入callable,需要再包装一层
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        System.out.println(futureTask.get());

    }
}

运行结果如下: 

我们对代码进行分析: 

三.优缺点总结

  1. 继承Thread类
    • 优点:继承Thread类可以直接重写run()方法,非常简单直观。
    • 缺点:由于Java不支持多继承,所以如果使用继承Thread类创建线程,就无法再继承其他类。
  2. 实现Runnable接口
    • 优点:实现Runnable接口可以避免单继承的限制,可以继续继承其他类。
    • 缺点:需要额外定义一个类来实现Runnable接口,并重写run()方法。
  3. 基于Lambda表达式
    • 优点:使用Lambda表达式可以更简洁地创建线程,不需要显式地创建一个新的类或实现接口。
    • 缺点:Lambda表达式可能会降低代码的可读性,特别是对于复杂的线程逻辑。
  4. 实现Callable接口
    • 优点:Callable接口可以返回线程执行的结果,可以通过Future对象获取线程的返回值。
    • 缺点:使用Callable接口创建线程相对复杂,需要使用ExecutorService来执行Callable任务,并获取返回值。

四.总结体会

继承Thread类和实现Runnable接口是最常见的线程创建方法,它们都可以实现多线程的功能。

使用Lambda表达式可以简化线程的创建过程,特别适合简单的线程逻辑。

实现Callable接口可以获取线程的返回值,适用于需要线程执行结果的场景。

在选择线程创建方法时,我们需要根据具体的需求和代码结构来选择合适的方法。

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

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

相关文章

基于SSM的网上药品售卖系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

衷心 祝愿

达之云衷心祝愿您&#xff0c;中秋国庆双节快乐&#xff0c;阖家幸福&#xff01;感谢您们一直以来对达之云的关注与支持。 双节来临之际&#xff0c;达之云发布全新产品——达之云CDP客户数据平台&#xff08;Dazdata CDP&#xff09;&#xff0c;致力于为中小企业提供互联网营…

【VUE复习·6】监视属性watch:用途、两种写法、简写、应用时注意事项(重点)、深度监视(重点)

总览 1.监视属性是用来干什么的&#xff1f; 2.监视属性的两种写法 3.应用时注意事项 4.深度监视 一、监视属性是用来干什么的&#xff1f; 1.用途 监视一个值&#xff08;可以是基本属性 data&#xff0c;或者是计算属性 computed&#xff09;是否被改变。如果此值被改变&…

品牌奖项+个人奖项双丰收,极智嘉全面展现自身硬核实力

最近&#xff0c;中国物流与采购联合会发布了2022年度科学技术奖获奖名单。在这份名单中&#xff0c;极智嘉与国药物流、南京医药、九州通医药以及多所高校合作&#xff0c;成功研发并应用了“面向医药流通的大规模机器人集群系统关键技术”。这项研究赢得了科学技术进步奖的一…

SpringCloud + SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题

title: “SpringCloud SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题” createTime: 2021-11-24T10:27:5708:00 updateTime: 2021-11-24T10:27:5708:00 draft: false author: “Atomicyo” tags: [“tomcat”] categories: [“java”] description: …

Win10检测和配置使用千兆网

注&#xff1a;一般只有比较旧的电脑才会是百兆网卡&#xff08;我的2019年笔记本dell灵越是百兆网卡瑞昱RTL810x/8139&#xff0c;离谱&#xff09;。 查看硬件是否为千兆网卡 cmd内执行如下&#xff08;100000000b100000kb100Mb&#xff0c;我这里显示的为百兆&#xff09;…

如何在不失去理智的情况下调试 TensorFlow 训练程序

一、说明 关于tensorflow的调试&#xff0c;是一个难啃的骨头&#xff0c;除了要有耐力&#xff0c;还需要方法&#xff1b;本文假设您是一个很有耐力的开发者&#xff0c;为您提供一些方法&#xff1b;这些方法也许不容易驾驭&#xff0c;但是依然强调您只要有耐力&#xff0c…

Multisim14.0仿真(二十七)基于UC3842的反激式开关电源的设计及仿真

一、UC3842简介&#xff1a; UC3842为固定频率电流模式PWM控制器。它们是专门为OFF−线和直流到直流转换器应用与最小的外部组件。内部实现的电路包括用于精确占空比控制的修剪振荡器、温度补偿参考、高增益误差放大器、电流传感比较器和理想适合于驱动功率MOSFET的高电流温度极…

Unity中Shader的前向渲染路径ForwardRenderingPath

文章目录 前言一、前向渲染路径的特点二、渲染方式1、逐像素(效果最好)2、逐顶点(效果次之)3、SH球谐(效果最差) 三、Unity中对灯光设置 后&#xff0c;自动选择对应的渲染方式1、ForwardBase仅用于一个逐像素的平行灯&#xff0c;以及所有的逐顶点与SH2、ForwardAdd用于其他所…

pytorch入门篇

文章目录 张量张量的创建直接创建依据数值创建依概率分布创建张量 张量的操作张量的拼接与切分张量索引张量变换 线性回归模型 神经网络softmaxsoftmax实现自动求导transforms方法 迁移学习保存和加载模型 张量 张量&#xff08;Tensors&#xff09;类似于NumPy的ndarrays&…

不同管径地下管线的地质雷达响应特征分析

不同管径地下管线的地质雷达响应特征分析 前言 以混凝土管线为例&#xff0c;建立了不同管径的城市地下管线模型&#xff0c;进行二维地质雷达正演模拟&#xff0c;分析不同管径管线的地质雷达响应特征。 文章目录 不同管径地下管线的地质雷达响应特征分析前言1、管径50cm2、…

【项目开发 | C语言项目 | C语言课程管理系统】

本项目是一个简易的课程管理系统&#xff0c;为了帮助用户管理大学课程信息。用户可以进行课程的添加、删除、查看、搜索和修改等操作。适用于初学者学习c语言&#xff0c;也适用于高校学生课程设计&#xff0c;毕业设计参考。 一&#xff0c;开发环境需求 操作系统&#xff1…

DownloadWithEscaping/下载数据并且转义返回数据, DownloadWithCombine/下载数据并且组合数据 的使用

1. DownloadWithEscaping 下载数据并且转义返回数据 1.1 实现 struct PostDataModel:Identifiable, Codable {let userId: Intlet id: Intlet title: Stringlet body: String }/// ViewModel class DownloadWithEscapingViewModel: ObservableObject{Published var posts: [Po…

【LeetCode-中等题】654.最大二叉树

文章目录 题目方法一&#xff1a;递归 题目 方法一&#xff1a;递归 class Solution {int[] num null; public TreeNode constructMaximumBinaryTree(int[] nums) {num nums;return myTree(0,num.length-1);}public TreeNode myTree( int begin , int end){if(begin > end…

什么是RESTful API?它的设计原则是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是RESTful API&#xff1f;RESTful API的设计原则示例 ⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感…

【数据结构】直接插入排序 希尔排序(一)

目录 一&#xff0c;排序的概念 二&#xff0c;直接插入排序 1&#xff0c;基本思想 2&#xff0c;基本思路 3&#xff0c;思路实现 三&#xff0c;希尔排序 1&#xff0c;希尔排序的特性总结&#xff1a; 2&#xff0c;思路实现&#xff1a; 一&#xff0c;排序的概念…

基于SpringBoot+Vue 的游戏分享网站

1 简介 基于Java SpringBoot 的游戏分享网站&#xff0c;本系统主要包括管理员和用户两个角色组成&#xff1b;主要包括首页、个人中心、用户管理、游戏类型管理、游戏文章管理、交流论坛、系统管理等功能的管理系统。 文章首发地址 2 技术栈 开发语言&#xff1a;Java 框架…

EV代码签名证书的作用有哪些?如何获取呢?

我们都知道&#xff0c;黑客们往往会通过篡改软件代码来进行各种恶意行为&#xff0c;例如加入病毒、木马、恶意代码等&#xff0c;为了确保软件代码的完整性和可信任性&#xff0c;代码签名证书诞生了。代码签名证书又分为普通代码签名证书和EV代码签名证书&#xff0c;我们在…

P1003 [NOIP2011 提高组] 铺地毯(Arknights!)

[NOIP2011 提高组] 铺地毯 题目描述 为了准备一个独特的颁奖典礼&#xff0c;组织者在会场的一片矩形区域&#xff08;可看做是平面直角坐标系的第一象限&#xff09;铺上一些矩形地毯。一共有 n n n 张地毯&#xff0c;编号从 1 1 1 到 n n n。现在将这些地毯按照编号从小…

「私信分析」上线,帮助企业进行私信管理,提升营销线索转化

企业在新媒体矩阵建设过程中会出现各种各样的业务难题&#xff0c;为了更好地服务客户&#xff0c;矩阵通将秉持“为客户带来实际业务价值”的原则不断地优化产品功能。 矩阵通是新榜旗下的新媒体数字化内容资产管理SaaS&#xff0c;可以帮助企业解决跨平台账号运营难题&#x…