【JavaEE初阶】认识线程、创建线程

1. 认识线程(Thread)

1.1 概念

1) 线程是什么

一个线程就是一个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码.

举例:

还是回到我们之前的银⾏的例⼦中。之前我们主要描述的是个⼈业务,即⼀个⼈完全处理⾃⼰的业务。我们进⼀步设想如下场景:
⼀家公司要去银⾏办理业务,既要进⾏财务转账,⼜要进⾏福利发放,还得进⾏缴社保。
如果只有张三⼀个会计就会忙不过来,耗费的时间特别⻓。为了让业务更快的办理好,张三⼜找来两位同事李四、王五⼀起来帮助他,三个⼈分别负责⼀个事情,分别申请⼀个号码进⾏排队,⾃此就有了三个执⾏流共同完成任务,但本质上他们都是为了办理⼀家公司的业务。 此时,我们就把这种情况称为多线程,将⼀个⼤任务分解成不同⼩任务,交给不同执⾏流就分别排队执⾏。其中李四、王五都是张三叫来的,所以张三⼀般被称为主线程(Main Thread)。

2) 为何要有线程  

首先,我们来说一下进程。在多任务操作系统中,希望系统能够同时运行多个程序,这就引入了进程。如果是单任务的操作系统,就完全不涉及进程,也不需要管理(进程),更不需要调度。本质上说,进程是解决”并发编程“问题的,事实上,进程也可以很好地解决并发编程这样的问题。

但是在一些特定的环境下,进程的表现不尽人意,比如,有些场景下,需要频繁的创建和销毁进程,举例,最早的web开发,是使用C语言来编写的服务器程序(基于一种CGI这样的技术,其基于多进程的编程模式),服务器同一时刻会收到很多请求,针对每个请求,都会创建出一个进程,给这个请求提供一定的服务,返回对应的响应;一旦这个请求处理完了,此时这个进程就要销毁了。如果请求很多,就意味着服务器要不停地创建进程、销毁进程,此时使用多进程编程,系统的开销就会很大(开销主要体现在资源的申请和释放上)。 

⾸先, "并发编程" 成为 "刚需"

  • 单核 CPU 的发展遇到了瓶颈. 要想提⾼算力, 就需要多核 CPU,而并发编程能更充分利⽤多核 CPU资源
  • 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做⼀些其他的⼯作, 也需要⽤到并发编程
其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.
  • 创建线程⽐创建进程更快.
  • 销毁线程⽐销毁进程更快.
  • 调度线程⽐调度进程更快.

多线程并发编程,效率更高,尤其是对于java进程,是要启动java虚拟机的,启动java虚拟机开销是更大的,搞多个java进程,就要多个java虚拟机。所以,java中不太去鼓励多进程编程。

最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 "线程池"(ThreadPool) 和 "协程"
(Coroutine)

3) 进程和线程的区别

  • 进程是包含线程的. 每个进程⾄少有⼀个线程存在,即主线程。
  • 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间.
⽐如之前的多进程例⼦中,每个客户来银⾏办理各⾃的业务,但他们之间的票据肯定是不想让别⼈知道的,否则钱不就被其他⼈取⾛了么。⽽上⾯我们的公司业务中,张三、李四、王五虽然是不同的执⾏流,但因为办理的都是⼀家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最⼤区别。
  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
  • ⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带⾛(整个进程崩溃).

另外注意:

  1. 同一个进程中的线程之间,可能会互相干扰,引起线程安全问题
  2. 线程也不是越多越好,要能够合适,如果线程太多了,调度开销可能会非常明显 

 

4) Java 的线程 和 操作系统线程 的关系

线程是操作系统中的概念, 操作系统内核 实现了线程这样的机制, 并且对用户层提供了⼀些 API 供用户使用(例如 Linux 的 pthread 库)。
对于操作系统内核:
操作系统内核,是操作系统最核心部分的功能模块(管理硬件、给软件提供稳定的运行环境)。
操作系统 = 内核 + 配套的应用程序
这里用银行为例来说明一下:
当你到银行进行各种业务的办理的时候,都是需要在办事窗口前,给工作人员说清楚你的需求,由工作人员代办。我们知道银行中的办事窗口内部和银行大厅是分隔开的,你是进不去办事窗口内部的, 这里的办事窗口内部就相当于操作系统内核空间(内核态),你所在大厅则是用户空间(用户态)。 

为什么划分出用户态、内核态:

 最主要的目的,还是为了“稳定”。防止你的应用程序,把硬件设备或软件资源给搞坏了。系统封装了一些api,这些api都属于是一些“合法”的操作,应用程序只能调用这些api,这样就不至于对系统/硬件设备产生太大的危害。

假设让应用程序直接操作硬件,可能极端情况下,代码出现bug,就把硬件干烧了。

Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进⼀步的抽象和封装。

 1.2 第⼀个多线程程序

感受多线程程序和普通程序的区别:
  • 每个线程都是⼀个独立的执行流
  • 多个线程之间是 "并发" 执行的
class MyThread2 extends Thread {//Thread类不用导包,属于特殊的包java.long,该包默认自动导入@Overridepublic void run() {//run方法就是该线程的入口方法while (true) {System.out.println("hello run");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo2 {public static void main(String[] args) {//2、根据刚才的类,创建出具体的实例(线程实例,才是真正的线程)Thread t = new MyThread2();//3、调用Thread的start方法,才会真正调用系统api,在系统内核中创建出线程t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}

对于上述代码,运行结果为两个循环不停地同时输出(验证了多个线程之间是 "并发" 执行的)。

我们知道,若对于普通程序来说,当遇到一个无限循环,会停留在这个循环,不停的打印输出,后续的代码是执行不到的。然而这个多线程程序,两个循环都执行到了,是因为每个线程都是⼀个独立的执行流 。代码中 t.start() ,即调用start()之后会创建一个新的线程,该线程进入到 run 方法,进行循环;而此时main线程,这个主线程会继续自己的执行,执行后续代码,也进行循环。

这里可以使用 jconsole 命令观察线程

2. 创建线程的几种方法

  •  方法1 继承 Thread 类

我们上面写的第一个多线程程序就是用的该方法。

1、继承 Thread 来创建⼀个线程类:
class MyThread extends Thread {@Overridepublic void run() {System.out.println("这⾥是线程运⾏的代码");}
}
2、创建 MyThread 类的实例:
MyThread t = new MyThread();
3、调⽤ start 方法启动线程:
 t.start();  //调用start才会真正地创建线程
  • 方法2 实现 Runnable 接口

1、实现 Runnable 接口

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这⾥是线程运⾏的代码");}
}

 2、创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.

Thread t = new Thread(new MyRunnable());//或者另一种写法
Runnable runnable = new MyRunnable();
Thread t = new Thread(runnable);

 3、调用start方法

t.start(); // 线程开始运⾏

该方法完整代码示例:

class MyThread3 implements Runnable {@Overridepublic void run() {while (true) {System.out.println("hello runnable");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo3 {public static void main(String[] args) {
//        Runnable runnable = new MyThread3();
//        Thread t = new Thread(runnable);Thread t = new Thread(new MyThread3());t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}
对比上面两种方法:
  • 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
  • 实现 Runnable 接口, this 表示的是 MyRunnable 的引用,需要使用Thread.currentThread()来表示当前线程对象

其他创建方法

  • 匿名内部类创建 Thread 子类对象

// 使⽤匿名类创建 Thread ⼦类对象
Thread t1 = new Thread() {@Overridepublic void run() {System.out.println("使⽤匿名类创建 Thread ⼦类对象");}
};
  • 匿名内部类创建 Runnable 子类对象 

// 使⽤匿名类创建 Runnable ⼦类对象
Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("使⽤匿名类创建 Runnable ⼦类对象");}
});
  • lambda 表达式创建 Runnable 子类对象 

// 使⽤ lambda 表达式创建 Runnable ⼦类对象
Thread t3 = new Thread(() -> System.out.println("使⽤匿名类创建 Thread ⼦类对象"));
Thread t4 = new Thread(() -> {System.out.println("使⽤匿名类创建 Thread ⼦类对象");
});

3. 多线程的优势-增加运行速度

可以观察多线程在⼀些场合下是可以提高程序的整体运行效率的。
  • 使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
  • serial 串行的完成⼀系列运算. concurrency 使用两个线程并行的完成同样的运算.
public class ThreadAdvantage {// 多线程并不⼀定就能提⾼速度,可以观察,count 不同,实际的运⾏效果也是不同的private static final long count = 10_0000_0000;public static void main(String[] args) throws InterruptedException {// 使⽤并发⽅式concurrency();// 使⽤串⾏⽅式serial();}private static void concurrency() throws InterruptedException {long begin = System.nanoTime();// 利⽤⼀个线程计算 a 的值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {int a = 0;for (long i = 0; i < count; i++) {a--;}}});thread.start();// 主线程内计算 b 的值int b = 0;for (long i = 0; i < count; i++) {b--;}// 等待 thread 线程运⾏结束thread.join();// 统计耗时long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("并发: %f 毫秒%n", ms);}private static void serial() {// 全部在主线程内计算 a、b 的值long begin = System.nanoTime();int a = 0;for (long i = 0; i < count; i++) {a--;}int b = 0;for (long i = 0; i < count; i++) {b--;}long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("串⾏: %f 毫秒%n", ms);}
}
并发: 399.651856 毫秒
串行: 720.616911 毫秒

 

该篇是对多线程的初步认识,接下来我会继续更新多线程的相关内容,请多多关注!

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

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

相关文章

业务逻辑漏洞

业务逻辑漏洞 扫描器扫不出来 漏洞包括 暴力破解任意用户/密码登陆短信/邮箱轰炸验证码绕过/爆破/重放/回传用户名/手机号枚举(用户名枚举&#xff1a;当用户登录时&#xff0c;显示用户名不存在&#xff0c;或密码不正确&#xff0c;两个其中一个不正确就称为用户名枚举)越…

Python中的datetime库

1. datetime datetime是Python中用于处理日期和时间的类&#xff0c;它包含在datetime模块中。使用datetime类&#xff0c;我们可以创建表示特定日期和时间的对象&#xff0c;以及进行日期和时间的计算和操作。 from datetime import datetime, timedelta# 获取当前日期和时间…

20 章 多线程

20.1线程简介. 20.2创建线程 2.1继承Thread类 Thread 类是java.lang包中的一个类&#xff0c;从这个类中实例化的对象代表线程&#xff0c;程序员启动一个新线程需要建立Thread 实例。Thread类中常用的两个构造方法如下: public Thread():创建一个新的线程对象。 public Thre…

用C++和python混合编写数据采集程序?

之前看过一篇文章&#xff0c;主要阐述的就是多种语言混合编写爬虫程序&#xff0c;结合各种语言自身优势写一个爬虫代码是否行得通&#xff1f;觉得挺有意思的&#xff0c;带着这样的问题&#xff0c;我尝试着利用我毕生所学写了一段C和python混合爬虫程序&#xff0c;目前运行…

LeetCode Hot100 84.柱状图中最大的矩形

题目&#xff1a; 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 方法&#xff1a; 代码&#xff1a; class Solution {public int largestRectang…

成为AI产品经理——模型评估概述

目录 一、模型宣讲和评估的原因 二、模型宣讲 三、模型评估 1. 重要特征 ① 特征来源 ②特征意义 2.选择测试样本 3.模型性能和稳定性 一、模型宣讲和评估的原因 刘海丰老师提到他们在做一个金融AI产品未注重模型指标&#xff0c;过于注重业务指标&#xff0c;导致产生…

Python小知识

个人学习笔记&#xff0c;用于记录使用过程中好用的技巧、好用的库。 1 小知识 1.1 相对路径 1.2 打包Exe文件 命令&#xff1a; pyinstaller -F main.py其中-F&#xff1a;覆盖之前打包的文件 mian.py&#xff1a;需要打包的Python文件 PS&#xff1a;使用pyinstaller 5.1…

Docker,从入门到精通

1、DockerFile 介绍 dockerfile 是啥?dockerfile 用来构建 docker 镜像的文件。 具体步骤&#xff1a; 1、编写一个 dockerfile 文件 2、docker build 构造一个镜像 3、docker run 运行镜像 4、docker push 发布镜像 DockerFile 构建过程 1、每个保留关键字都必须是大…

人工智能-优化算法之凸集

凸性 凸性&#xff08;convexity&#xff09;在优化算法的设计中起到至关重要的作用&#xff0c; 这主要是由于在这种情况下对算法进行分析和测试要容易。 换言之&#xff0c;如果算法在凸性条件设定下的效果很差&#xff0c; 那通常我们很难在其他条件下看到好的结果。 此外&…

【Vue】绝了!还有不懂生命周期的?

生命周期 Vue.js 组件生命周期&#xff1a; 生命周期函数&#xff08;钩子&#xff09;就是给我们提供了一些特定的时刻&#xff0c;让我们可以在这个周期段内加入自己的代码&#xff0c;做一些需要的事情; 生命周期钩子中的this指向是VM 或 组件实例对象 在JS 中&#xff0c;…

微服务实战系列之Cache

前言 欢迎来到Cache&#xff08;缓存&#xff09;的世界&#xff01; 自从世界第一台计算机诞生之日起&#xff0c;人们对效率的渴望逐步增强。从CPU到存储&#xff0c;从芯片到内存&#xff0c;一批又一批的先驱以一种孜孜不倦的“工匠”精神&#xff0c;为计算机运行效率的提…

华为P40无法链接adb的解决记录

真的很讨厌华为的设备&#xff0c;很多东西啥设备都能跑得好好的&#xff0c;就华为会出问题&#xff0c;简直就是手机界的IE。 情况&#xff1a;突然无法链接adb到P40&#xff0c;拔插无效&#xff0c;关闭开发人员选项再打开也无效&#xff0c;撤销USB调试授权也无效&#x…

西南科技大学电路分析基础实验A1(一阶电路的设计)

目录 一、实验目的 二、实验设备 三、预习内容(如:基本原理、电路图、计算值等) 四、实验数据及结果分析(预习写必要实验步骤和表格) 1. 观测一阶电

leetcode:有效的括号

题目描述 题目链接&#xff1a;20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 题目分析 题目给了我们三种括号&#xff1a;&#xff08;&#xff09;、{ }、[ ] 这里的匹配包括&#xff1a;顺序匹配和数量匹配 最优的思路就是用栈来解决&#xff1a; 括号依次入栈…

用于计算机屏幕安全摄像头系统:Screen Anytime Crack

Screen Anytime 是一款软件&#xff0c;旨在自动将整个用户会话或 PC/服务器/VM/Kiosk 的 /RDP/Citrix/RemoteApp 会话的屏幕活动记录到视频日志文件中&#xff0c;以用于记录、审核和监控目的。通过重播其高度压缩的视频&#xff0c;您可以轻松回顾单台计算机或一组服务器/PC …

Joint Bilateral Upsampling

Abstract 图像分析和增强任务&#xff08;例如色调映射、着色、立体深度和蒙太奇&#xff09;通常需要在像素网格上计算解决方案&#xff08;例如&#xff0c;曝光、色度、视差、标签&#xff09;。计算和内存成本通常要求在下采样图像上运行较小的解决方案。尽管通用上采样方…

只需十分钟快速入门Python,快速了解基础内容学习。零基础小白入门适用。

文章目录 简介特点搭建开发环境版本hello world注释文件类型变量常量数据类型运算符和表达式控制语句数组相关函数相关字符串相关文件处理对象和类连接mysql关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源…

matlab绘图函数plot和fplot的区别

一、背景 有的函数用plot画就会报错&#xff0c;显示数据必须为可转换为双精度值的数值、日期时间、持续时间、分类或数组。 如下图所示&#xff1a; 但用fplot函数就没有问题&#xff0c;因此这里记录一下两者的区别&#xff0c;如果使用不当&#xff0c;画出的图可能就是下…

以太网通讯协议小结--持续更新中

一、以太网介绍 以太网是一种产生较早&#xff0c;使用相当广泛的局域网技术&#xff0c;局域网就是一个区域的网络互联&#xff0c;可以使办公室也可以是学校等等&#xff0c;大小规模不一。 目前以太网根据速度等级分类大概分为&#xff1a;标准以太网&#xff08;10Mbit/s…

Redis-Redis高可用集群之水平扩展

Redis3.0以后的版本虽然有了集群功能&#xff0c;提供了比之前版本的哨兵模式更高的性能与可用性&#xff0c;但是集群的水平扩展却比较麻烦&#xff0c;今天就来带大家看看redis高可用集群如何做水平扩展&#xff0c;原始集群(见下图)由6个节点组成&#xff0c;6个节点分布在三…