创建线程的4种方法

目录

一.前言

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{@Overridepublic 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{@Overridepublic 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{@Overridepublic 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(){@Overridepublic 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{@Overridepublic 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() {@Overridepublic 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;@Overridepublic 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.rhkb.cn/news/142925.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自定义ElementPlus主题颜色

构建工具采用Vite CSS预处理器采用Sass 一.准备定制化的样式文件 1.安装Sass npm i sass -D 2.创建好文件目录 3.书写样式 ElementPlus默认样式. //index.scss/* 只需要重写你需要的即可 */ forward element-plus/theme-chalk/src/common/var.scss with ($colors: (prim…

pytorch固定随机数中种子

1、添加到yolov7的utils/general.py文件最下面 import pkg_resources as pkg def check_version(current0.0.0, minimum0.0.0, nameversion , pinnedFalse, hardFalse, verboseFalse):# Check version vs. required versioncurrent, minimum (pkg.parse_version(x) for x in …

【Verilog 教程】6.6Verilog 仿真激励

关键词&#xff1a;testbench&#xff0c;仿真&#xff0c;文件读写 Verilog 代码设计完成后&#xff0c;还需要进行重要的步骤&#xff0c;即逻辑功能仿真。仿真激励文件称之为 testbench&#xff0c;放在各设计模块的顶层&#xff0c;以便对模块进行系统性的例化调用进行仿真…

c#用Gnuplot画图源码

直接调用这个类即可&#xff0c;需要下载个GnuPlot安装下。 // Author: Leonardo Tazziniusing System; using System.Diagnostics; using System.Drawing; using System.IO; using System.Windows.Forms;/// <summary> /// Tested with Gnuplot 5.2 /// </summary&g…

云原生微服务治理经典框架之Spring Cloud Alibaba核心技术与实战案例

系列文章目录 送书第一期 《用户画像&#xff1a;平台构建与业务实践》 送书活动之抽奖工具的打造 《获取博客评论用户抽取幸运中奖者》 送书第二期 《Spring Cloud Alibaba核心技术与实战案例》 文章目录 系列文章目录1、云原生如何做微服务治理&#xff1f;2、微服务治理框…

类和对象:运算符重载

本篇文章来介绍一下C中的运算符重载&#xff0c;以及与运算符重载有关的三个默认默认成员函数&#xff1a;赋值运算符重载&#xff0c;普通对象取地址与const对象取地址操作符重载&#xff0c;也就是下面图片中6个默认成员函数的后三个&#xff0c;前三个默认成员函数在之前文章…

【go语言】结构体

结构体 结构体定义 type name struct{value1 type1value2 type2...... }组成结构体的数据称为字段&#xff0c;每一个字段有一个类型和一个名字&#xff0c;每一个字段的名字必须唯一&#xff0c;字段的类型任意。 创建结构体 type myStruct struct{i1 intf1 float32str st…

HDLBits-Edgedetect

刚开始写的代码如下&#xff1a; module top_module (input clk,input [7:0] in,output [7:0] pedge );reg [7:0] in_pre;always (posedge clk)begin in_pre < in;endassign pedge in & ~in_pre; endmodule但是提交结果是错误的。猜想原因如下&#xff1a; assign p…

某音网页端 X-Bogus 参数

逆向目标 目标&#xff1a;某音网页端用户信息接口 X-Bogus 参数 接口&#xff1a;aHR0cHM6Ly93d3cuZG91eWluLmNvbS9hd2VtZS92MS93ZWIvdXNlci9wcm9maWxlL290aGVyLw 什么是 JSVMP&#xff1f; JSVMP 全称 Virtual Machine based code Protection for JavaScript&#xff0c;即 …

【网络八股】TCP八股

网络八股 请简述TCP/IP模型中每层的作用&#xff0c;典型协议和典型设备介绍一下三次握手的过程介绍一下四次挥手的过程必须三次握手吗&#xff0c;两次不行吗&#xff1f;为什么ACK数据包消耗TCP的序号吗三次握手中可以携带应用层数据吗四次挥手时&#xff0c;可以携带应用层数…

crypto:丢失的MD5

题目 得到一个md5.py 运行一下&#xff0c;发现报错&#xff0c;修改一下 运行之后又报错 报错原因是算法之前编码 正确的代码为 import hashlib for i in range(32,127):for j in range(32,127):for k in range(32,127):mhashlib.md5()m.update((TASC chr(i) O3RJMV c…

【Java SE】Lambda表达式

目录 ♫什么是Lambda表达式 ♫Lambda表达式的语法 ♫函数式接口 ♫Lambda表达式的使用 ♫变量捕获 ♫ Lambda表达式在集合中的使用 ♪Collection的foreach()&#xff1a; ♪List的sort()&#xff1a; ♪Map的foreach() ♫什么是Lambda表达式 Lambda 表达式是 Java SE 8中一个…

SpringMVC 学习(二)Hello SpringMVC

3. Hello SpringMVC (1) 新建 maven 模块 springmvc-02-hellomvc (2) 确认依赖的导入 (3) 配置 web.xml <!--web/WEB-INF/web.xml--> <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee…

通用CI/CD软件平台TeamCity推出代理终端功能,谁能从中获益?

JetBrains官方在TeamCity中推出代理终端&#xff1a;这项新功能专门用于帮助用户轻松查看代理上的系统日志、检查已安装的软件&#xff0c;以及直接从 TeamCity 的 UI 调试特定代理问题。 TeamCity是一个通用的 CI/CD 软件平台&#xff0c;可以实现灵活的工作流、协作和开发做…

抖音短视频seo矩阵系统源代码开发系统架构及功能解析

短视频seo源码&#xff0c;短视频seo矩阵系统底层框架上支持了从ai视频混剪&#xff0c;视频批量原创产出&#xff0c;云存储批量视频制作&#xff0c;账号矩阵&#xff0c;视频一键分发&#xff0c;站内实现关键词、短视频批量搜索排名&#xff0c;数据统计分类多功能细节深度…

深入MySQL数据库进阶实战:性能优化、高可用性与安全性

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 MySQL是世界上最流行的开…

ruoyi-nbcio增加websocket与测试页面

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 为了后面流程发起等消息推送&#xff0c;所以需要集成websocket。 1、后端增加websoket支持 首先在framework模块里的pom.xml增加websocket <dependency…

Element UI搭建首页导航和左侧菜单以及Mock.js和(组件通信)总线的运用

目录 前言 一、Mock.js简介及使用 1.Mock.js简介 1.1.什么是Mock.js 1.2.Mock.js的两大特性 1.3.Mock.js使用的优势 1.4.Mock.js的基本用法 1.5.Mock.js与前端框架的集成 2.Mock.js的使用 2.1安装Mock.js 2.2.引入mockjs 2.3.mockjs使用 2.3.1.定义测试数据文件 2…

如何优化网站排名(百度SEO指南与优化布局方法)

百度SEO指南介绍&#xff1a;蘑菇号-www.mooogu.cn 首先&#xff0c;为了提高网站的搜索引擎优化排名&#xff0c;需要遵循百度SEO指南的规则和标准。这包括使用符合规范的网站结构、页面内容的质量和与目标用户相关的关键词。避免使用非法技术和黑帽SEO的方法。 增加百度SEO…

Python——— 异常机制

&#xff08;一&#xff09;异常 工作中&#xff0c;程序遇到的情况不可能完美。比如&#xff1a;程序要打开某个文件&#xff0c;这个文件可能不存在或者文件格式不对&#xff1b;程序在运行着&#xff0c;但是内存或硬盘可能满了等等。 软件程序在运行过程中&#xff0c;非常…