多线程(JavaEE初阶系列2)
目录
前言:
1.什么是线程
2.为什么要有线程
3.进程与线程的区别与联系
4.Java的线程和操作系统线程的关系
5.多线程编程示例
6.创建线程
6.1继承Thread类
6.2实现Runnable接口
6.3继承Thread,使用匿名内部类
6.4实现Runnable接口,使用匿名内部类
6.5lambda表达式创建Runnable子类对象
7.Thread类及常见的方法
7.1Thread中常见的构造方法
7.2Thread的几个常见属性
结束语:
前言:
这节中小编就来和大家聊一聊多线程是什么以及需要我们掌握多线程程序的编写、多线程的状态、什么是线程不安全及解决思路以及掌握synchronized、volatile关键字。在上节博客中给大家讲到了进程和进程管理,我们讲解了为什么要使用调度,CPU的按照并发的方式来执行进程的,在PCB中也提供了一些属性,里面有进程的优先级、进程的状态、进程的上下文、进程的记账信息......也给大家讲解了引入进程的目的就是为了能够实现多个任务并发执行的效果。接下来小编就给大家讲解一下什么是线程,线程与进程之间又有什么联系。
1.什么是线程
一个线程就是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行着多份代码。
2.为什么要有线程
首先“并发编程”称为“刚需”。
- 单核CPU的发展遇到了瓶颈,要想提高算力,就需要多核CPU,而并发编程能够更充分利用多核CPU资源。
- 有些任务场景需要“等待IO”,为了让等待IO的时间能够去做一些其他的工作,也需要用到并发编程。
其次,虽然多进程也能实现并发编程,但是是线程比进程更轻量。
- 创建线程比创建进程更快。
- 销毁线程比销毁进程更快。
- 调度线程比调度进程更快。
最后,线程虽然比进程轻量,但是人们还不满足,于是又有了“线程池”和“协程”。
- 有关于线程池的话题我们后面在介绍,关于协程的话题这里我们不做过多的讨论。
3.进程与线程的区别与联系
进程有一个重大的问题就是比较重量,如频繁的创建/销毁进程,成本会比较高。
所以我们又引出了线程的概念。那么线程与进程之间到底有什么联系和区别呢?我们接着往下看。
进程是包含线程的。每一个进程至少有一个线程存在,即主线程。一个进程里可以有一个线程也可以有多个线程,每个线程之间都是一个独立存在的执行流,多个线程之间也是并发执行的。这里注意多个线程可能是在多个CPU核心上同时运行,也可能是在一个CPU核心上,通过快速调度,并发运行。操作系统真正的调度是在调度线程而不是在调度进程。
注意:线程是操作系统调度运行的基本单位!!!进程是操作系统资源分配的基本单位!!!
一个进程中的多个线程之间,共用一份系统资源。
- 内存空间。
- 文件描述符表。
注意:只有在进程启动的时候,创建第一个线程的时候需要花成本去申请系统资源。一旦进程(第一个线程)创建完毕,此时后续再创建线程就不必在申请资源了,这样创建和销毁的效率就会大大提高。
下面小编给大家举个例子方便大家更好的理解进程与线程。
所以通过上述的例子再给大家总结一下进程与线程之间的区别:
- 进程包含线程。
- 进程有自己独立的内存空间和文件描述符表,同一个进程中的多个线程之间共享一份地址空间和文件描述符表。
- 进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位。
- 进程之间具有独立性,一个进程挂了不会影响其他进程;同一个进程里的多个线程之间,一个线程挂了,可能会把整个进程带走,影响其他进程。
4.Java的线程和操作系统线程的关系
线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些API供用户使用。
Java标准库中Thread类可以视为是对操作系统提供的API进行了进一步的抽象和封装。
5.多线程编程示例
上面说了这么多的概念,接下来还是带着大家一起在代码中切实的感受一下究竟什么是多线程。
代码展示:
package Thread;
class MyThread extends Thread{@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();//start会创建新的线程。t.start();//run不会创建新的线程。run是在main线程中执行的。//t.run();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
代码分析:
在上述代码中我们看到我们写了一个类MyThread并继承了Thread类,在java中标准库提供了一个类Thread,它能够表示一个线程。
在继承Thread的时候我们需要对父类中的run()方法进行重写。
- 在主函数中我们先创建出来了一个MyThread的实例。
Thread t = new MyThread();
- 紧接着我们就要启动线程。
t.strat();
在上述代码中我们涉及到了两个线程。
- main方法所对应的线程(一个进程里面至少有一个线程)也可以称为主线程。
- 通过t.strat创建一个新的线程。
结果分析:
我们执行代码之后看到的效果是“hello t”和“hello main”是交替打印的,但又不是严格意义上的交替打印。按照我之前的单线程的想法,如果我们执行main线程中的进入while循环之后由于是死循环,那么按照之前的想法应该是出不来的,应该是要一值打印“hello main”的,但是这里并不是我们想象中的那样,而是和另一个线程中的一起交替打印,那就说明这里是启动了两个线程,两个线程分别独立运行。多线程在CPU上的调度是不确定的是随机的。所以我们看到的就是不规律的交替打印。这也就是我们上述中提到的一个进程中的多个线程并发执行的过程。
同时我们可以借助jak里面的工具jconsole来分析java里的线程。
在下面的路径下找到安装路径,然后双击打开运行。
点击线程,在下面可以看到很多线程这里你会发现有很多线程,这里不只有咱们创建出来的两个。除了标记出来的两个线程之外其他都是JVM自己创建出来的。
描述出了当前这两线程执行到哪里了。
6.创建线程
6.1继承Thread类
我们可以使用Thread,重写run方式来创建线程。
代码展示:
package Thread;
//使用继承Thread,重写run方法来创建线程
class MyThread extends Thread{@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();//start会创建新的线程。t.start();//run不会创建新的线程。run是在main线程中执行的。//t.run();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
6.2实现Runnable接口
使用实现Runable,重写run。
代码展示:
package Thread;
//使用Runnable接口来实现线程创建
//1.实现Runnable接口
class MyRunnable implements Runnable{@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo2 {public static void main(String[] args) {//2.创建爱你Thread类实例,调用Thread的构造方法是将Runnable对象作为target参数。MyRunnable runnable = new MyRunnable();Thread t = new Thread(runnable);//3.调用start方法。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()
6.3继承Thread,使用匿名内部类
代码展示:
package Thread;
//继承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) {e.printStackTrace();}}}};//启动线程t.start();//主线程while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
6.4实现Runnable接口,使用匿名内部类
代码展示:
package Thread;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) {e.printStackTrace();}}}});//启动线程t.start();//主线程while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
6.5lambda表达式创建Runnable子类对象
这个也是创建线程最推荐的写法,使用lambda表达式也是最直观的简单的写法!!!
前面也给大家讲解过有关于lambda表达式的用法,如果还有不会的同学请点击下面的链接先去看看lambda表达式的使用吧!!!(http://t.csdn.cn/zEPFB)
代码展示:
package Thread;
//使用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) {e.printStackTrace();}}});//启动线程t.start();//主线程while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
7.Thread类及常见的方法
Thread类是JVM用来管理线程的一个类,换句话说,每个线程都由一个唯一的Thread对象与之关联。Java代码中的Thread对象和操作系统中的线程是一一对应的。
用我们上述的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而Thread类的对象就是用来描述一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。
7.1Thread中常见的构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
代码演示案例:
package Thread;public class ThreadDemo6 {public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"我的线程");t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
我们可以看到在上述运行的线程中就会出现自己命名的线程名字。
7.2Thread的几个常见属性
属性 | 获取 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
代码展示:
package Thread;public class ThreadDemo7 {public static void main(String[] args) {Thread t = new Thread(() -> {
// System.out.println("hello t");},"我的线程");t.start();System.out.println("线程ID是:" + t.getId());System.out.println("线程名称是:" + t.getName());System.out.println("线程状态是:" + t.getState());System.out.println("线程优先级是:" + t.getPriority());System.out.println("线程是否是后台线程:" + t.isDaemon());System.out.println("线程是否存活:" + t.isAlive());System.out.println("线程是否被中断:" + t.isInterrupted());}
}
结果展示:
解释getDaemon:
- true表示是后台线程。后台线程不会阻止java进程结束,哪怕后台线程还没有执行完,java进程该结束就会结束。
- false表示是前台线程。前台线程会阻止java进程结束,必须得java进程中的所有前台线程都执行完java进程才会结束。
注意:创建的线程默认是前台的,也可以通过setDaemon修改成后台线程。
解释isAlive:
是描述系统内核里的那个线程是否还存活。线程的入口方法执行完毕,此时系统中的对应的线程就没有了,此时调用该线程isAlive就是false。
结束语:
这节中小编主要是给大家分享了线程了概念、线程与进程之间的区别和联系以及如何创建线程。希望这节对大家学习JavaEE有一定的帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)
相关文章:

多线程(JavaEE初阶系列2)
目录 前言: 1.什么是线程 2.为什么要有线程 3.进程与线程的区别与联系 4.Java的线程和操作系统线程的关系 5.多线程编程示例 6.创建线程 6.1继承Thread类 6.2实现Runnable接口 6.3继承Thread,使用匿名内部类 6.4实现Runnable接口,使…...
Ubuntu20.04点Ubuntu software没反应,打不开的解决方案(Ubuntu笔记)
首先检查Ubuntu Software的状态,在终端输入:systemctl status snap.ubuntu-software.ubuntu-software.service 如果状态显示为inactive,则需要启动snap.ubuntu-software.ubuntu-software.service,在终端输入:sudo sys…...

力扣1114.按序打印-----题目解析
题目描述 解析: class Foo {public int a 0;public Foo() {}public void first(Runnable printFirst) throws InterruptedException {// printFirst.run() outputs "first". Do not change or remove this line.printFirst.run();a;}public void second…...
Centos7.9安全部署_防火墙配置_端口配置_协议配置_IP配置_全部亲测---记录022_大数据工作笔记0182
在我们平时搭建大数据平台的时候,由于防火墙的限制,会让搭建集群的时候,报各种错误,但是,有些网络环境要求比较严格的地方,防火墙又要求必须要放开,尤其是.. 有些网络环境会安全组进行定时扫描,说实话,我们用的很多开源软件,一般都是低版本的话都有漏洞,但是升级的话又会很容易…...

Tik Tok海外公会是什么?
在数字社交媒体领域,TikTok已成为全球性的热门应用之一,印尼市场也不例外。作为全球第四人口最多的国家,印尼的年轻人口众多,是一个极具潜力的市场。对于希望在印尼市场进行TikTok公会申请的机构来说,了解市场发展趋势…...
elasticsearch批量导入问题
Testvoid bulkIndex() throws IOException{List<Product> products new ArrayList<Product>();products.add(new Product("香烟",135,1)); // products.add(new Product("瓜子",154,2)); // products.add(new Product("矿…...

SAP安装笔记
1、准备安装介质,SWPM10SP25,51050829_NW750_JavaExport、SAP_HANA_CLIENT、kernel放到/sapcd/NetWeaver目录下 进入SWPM10SP25执行./sapinst安装 2、待出现 “Open your browser and paste the following URL address to access the G…...

速速收藏!程序员副业赚钱的8种模式
程序员副业赚钱的方式有很多,我总结了程序员兼职赚钱,增加收入的八种模式,希望能够给在迷茫的程序员一点参考! 投资理财 很多程序员多少都会买买基金、炒炒股作为一个小副业,由于程序员大多是理科生,有比…...

2、基于redis实现分布式锁
目录 2.1. 基本实现2.2. 防死锁2.3. 防误删2.4. redis中的lua脚本2.4.1 redis 并不能保证2.4.2 lua介绍 2.5. 使用lua保证删除原子性 2.1. 基本实现 借助于redis中的命令setnx(key, value),key不存在就新增,存在就什么都不做。同时有多个客户端发送setn…...

【问题记录】Ubuntu 22.04 环境下,程序报:段错误(核心已转储)怎么使用 core 文件和GDB调试器 解决?
目录 环境 问题情况 解决思路 原因分析 解决方法 番外知识 环境 VMware Workstation 16 Pro (版本:16.1.2 build-17966106)ubuntu-22.04.2-desktop-amd64 问题情况 本人在运行百万并发的服务端程序时,程序运行报:…...
9 Linux实操篇-实用指令
9 Linux实操篇-实用指令 文章目录 9 Linux实操篇-实用指令9.1 指定和修改运行级别-init/systemctl9.2 找回root密码9.3 Linux的指令说明9.3 帮助类-man/help9.4 文件目录类-pwd/ls/cd/mkdir/...9.5 时间日期类-date/cal9.6 搜索查找类-find/locate/which/grep9.7 压缩和解压类-…...

Hbase基础概念
HBase 一、HBase的数据模型1.HBase数据存储结构2.HBase存储概念3.HBase基本架构 二、HBase Shell1.DDL(Data Definition Language)1.namespace2.table 2.DML(Data Manipulation Language)1.写入数据2.读取数据3.删除数据 三、HBase组成架构1. Master架构…...
JTS-Angle角度类
目录: 获取AB连线与正北方向的角度求距离此点一定距离、一定夹角的点经纬度判断point点 在线段startPoint-etartPoint的左侧或者右侧米转换为弧度弧度转换为米 定义Point点 public class LatLngPoint {final static double RC 6378137;final static double RJ …...

pytest---环境切换(base-url)
前言 前面小编介绍了如何通过pytest的插件来实现自动化测试的环境的切换,当时使用的方法是通过钩子函数进行获取命令行参数值,然后通过提前配置好的参数进行切换测试环境地址,今天小编再次介绍一种方法,通过pytest的插件ÿ…...

linux跑代码,程序终止了,但资源没有释放。
linux跑代码,程序终止了,但资源没有释放。 程序终止,但是资源没有释放. kill -9 5062完成。 linux终止进程...

数据结构--线性表2-1
目录 一、线性结构的定义 二、线性表的表示 三、顺序表的实现(或操作) 1、修改: 2、插入: 四、顺序表的运算效率分析:时间效率分析: 一、线性结构的定义 若结构时非空有限集,则有且仅有一个…...

网访问内网机器:基于frp的内网穿透
随缘更新些我自己的博客网站里的文章吧 因为经常需要远程访问自己的机器,所以写一个博客记录一下 公网访问内网机器:基于frp的内网穿透 从公网中访问自己的私有设备向来是一件难事儿。 1. 为什么需要内网穿透? A. 计算机网络 如何在自己的机…...

【Spring框架】Spring读取与存储综合练习
练习 在 Spring 项⽬中,通过 main ⽅法获取到 Controller 类,调⽤ Controller ⾥⾯通过注⼊的⽅式调⽤ Service 类,Service 再通过注⼊的⽅式获取到 Repository 类,Repository 类⾥⾯有⼀个⽅法构建⼀个 User 对象,返…...

Python实现指定区域桌面变化监控并报警
在这篇博客中,我们将使用Python编程语言和一些常用的库来实现一个简单的区域监控和变化报警系统。我们将使用Tkinter库创建一个图形界面,允许用户选择监控区域,并使用OpenCV库进行图像处理和相似性比较,以检测区域内的变化&#x…...

【数据结构】实验五:栈
实验五 栈 一、实验目的与要求 1)熟悉栈的类型定义和基本操作; 2)灵活应用栈解决具体应用问题。 二、实验内容 1、判断回文数,回文是指正读反读均相同的字符序列,如“1221”和“12321”均是回文,但“…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...

小智AI+MCP
什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析:AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github:https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…...

Canal环境搭建并实现和ES数据同步
作者:田超凡 日期:2025年6月7日 Canal安装,启动端口11111、8082: 安装canal-deployer服务端: https://github.com/alibaba/canal/releases/1.1.7/canal.deployer-1.1.7.tar.gz cd /opt/homebrew/etc mkdir canal…...