创建线程的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.mzph.cn/news/89170.shtml

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

相关文章

Android系统之编译Intel5.1问题解决

1.jdk问题: ************************************************************ You are attempting to build with the incorrect version of java.Your version is: openjdk version "1.7.0_75" OpenJDK Runtime Environment (build 1.7.0_75-b13) OpenJDK 64-Bit Se…

微服务线上问题排查困难?不知道问题出在哪一环?那是你还不会分布式链路追踪

咱们以前单体应用里面有很多的应用和功能&#xff0c;依赖各个功能之间相互调用&#xff0c;使用公共的代码包等等&#xff0c;排查问题&#xff0c;使用类似于 gdb/dlv 工具或者直接查看代码日志&#xff0c;进行定位和分析 但是现在我们基本上都是微服务架构了&#xff0c;将…

JavaScript入门——(2)基础语法(上)

一、JavaScript介绍 1.1 JavaScript是什么 1.1.1 JavaScript是什么&#xff1f; JavaScript是一种运行在客户端&#xff08;浏览器&#xff09;的编程语言&#xff0c;实现人机交互效果。 注意&#xff1a;HTML和CSS是标记语言。 1.1.2 作用&#xff08;做什么&#xff1f…

2023最新最详细软件测试技术面试题【含答案】

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 有这样一个面试题&#xff1a;在一个Web测试页面上&#xff0c;有一个输入框&#xff0c;一个计数器&…

【STM32单片机】u8g2智能风扇设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用STM32F103C8T6单片机控制器&#xff0c;使用按键、IIC OLED模块、DS18B20温度传感器、直流电机、红外遥控等。 主要功能&#xff1a; 初始化后进入温度显示界面&#xff0c;系统初始状态为手动…

软件测试工作步骤详情

软件测试步骤按照研发阶段一般分为5个部分&#xff1a;单元测试、集成测试、确认测试、系统测试、验收测试&#xff0c;下面将不同阶段需要的一些工作内容做一下梳理希望可以帮助到大家。 一、单元测试的内容&#xff1a;&#xff08;白盒为主&#xff0c;黑盒为辅&#xff09;…

Ubuntu 安装 CUDA 与 CUDNN GPU加速引擎

一、NVIDIA&#xff08;英伟达&#xff09;显卡驱动安装 NVIDIA显卡驱动可以通过指令sudo apt purge nvidia*删除以前安装的NVIDIA驱动版本&#xff0c;重新安装。 1.1. 关闭系统自带驱动nouveau 注意&#xff01;在安装NVIDIA驱动以前需要禁止系统自带显卡驱动nouveau&#xf…

Diffusion Autoencoders: Toward a Meaningful and Decodable Representation

Diffusion Autoencoders: Toward a Meaningful and Decodable Representation (Paper reading) Konpat Preechakul, VISTEC, Thailand, CVPR22 Oral, Cited:117, Code, Paper 1. 前言 扩散概率模型 (DPM) 在图像生成方面取得了显着的质量&#xff0c;可与 GAN 相媲美。但是与…

独立站引流,如何在Reddit进行营销推广?

Reddit是目前最被忽视却最具潜力的社交媒体营销平台之一&#xff0c;它相当于国内的百度贴吧&#xff0c;是美国最大的论坛&#xff0c;也是美国第五大网站&#xff0c;流量仅次于Google、Youtube、Facebook以及亚马逊。 如果会玩&#xff0c;Reddit也可以跟其他的社交媒体营销…

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…

扫地机器人经营商城小程序的作用是什么

扫地机器人对人们生活大有帮助&#xff0c;近些年也有不少企业开创品牌&#xff0c;在电商平台每年销量也非常高&#xff0c;同行竞争激烈及私域化程度加深情况下&#xff0c;虽然第三方平台或线下方式也有生意&#xff0c;但互联网电商发展也为商家们带来了诸多痛点。 那么通…

【图像去噪】【TGV 正则器的快速计算方法】通过FFT的总(广义)变化进行图像去噪(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码实现 &#x1f4a5;1 概述 【图像去噪】【TGV 正则化器的快速计算方法】通过FFT的总&#xff08;广义&#xff09;变换进行图像去噪&#xff0c;可提供更自然的恢复图像。…

uniapp:不同权限设置不同的tabBar

1、在pages.json里&#xff0c;将所有tabBar涉及的页面都加进来。 我这里使用username来动态显示tabBar。 jeecg用户显示&#xff1a;首页&#xff0c;订单&#xff0c;消息&#xff0c;发现&#xff0c;我的&#xff0c;一共5个tabBar。 admin用户显示&#xff1a;首页&…

青大数据结构【2022】

关键字&#xff1a; next数组、下三角矩阵、完全二叉树结点、静态分布动态分布、迪杰斯特拉最短路径、二叉排序树失败ASL、排序比较、二叉排序树中序遍历、链表删除最大值 一、单选 二、简答 三、应用 四、算法分析 五、算法设计

「Go框架」gin框架是如何处理panic的?

本文我们介绍下recover在gin框架中的应用。 首先&#xff0c;在golang中&#xff0c;如果在子协程中遇到了panic&#xff0c;那么主协程也会被终止。如下&#xff1a; package mainimport ("github.com/gin-gonic/gin" )func main() {r : gin.Default()// 在子协程中…

轻松使用androidstudio交叉编译libredwg库

对于安卓或嵌入式开发者而言,交叉编译是再熟悉不过的操作了,可是对于一些刚入门或初级开发者经常会遇到这样的问题:如何交叉编译C++库来生成安卓下的so库呢? 最近有一些粉丝找到我求救,那么我最近刚好有空大致研究了下,帮他们成功编译了其中一个libredwg的C++库,这篇文章…

LeetCode01

LeetCode01 两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和 为目标值 target 的那两个整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你…

Lua多脚本执行

--全局变量 a 1 b "123"for i 1,2 doc "Holens" endprint(c) print("*************************************1")--本地变量&#xff08;局部变量&#xff09; for i 1,2 dolocal d "Holens2"print(d) end print(d)function F1( ..…

Java中的IO流的缓冲流

不爱生姜不吃醋⭐️ 如果本文有什么错误的话欢迎在评论区中指正 与其明天开始&#xff0c;不如现在行动&#xff01; 文章目录 &#x1f334;IO流体系结构&#x1f334;缓冲流1.提高效率的原理2.缓冲流的类型3.字符缓冲流两个特有方法 &#x1f334;总结 &#x1f334;IO流体系…

git学习使用

git使用 1、cmd #查看版本 git version2、初识 Git GUI: Git提供的图形界面工具 Git Bash: Git提供的命令行工具 1.打开Git Bash2.设置自己的用户名和邮箱地址git config --global user.name "xxx"git config --global user.email "123456789163.com"查…