探索Java多线程的核心概念与实践技巧,带你从入门到精通!

各位看官早安午安晚安呀

如果您觉得这篇文章对您有帮助的话

欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦

今天我们来学习多线程编程-"掌握线程创建、管理与安全"

上一节课程我们铺垫了一系列的东西,引出来了我们的多线程,接下来就让小编带领大家一起进入多线程的世界吧!!!

目录

上一节课程我们铺垫了一系列的东西,引出来了我们的多线程,接下来就让小编带领大家一起进入多线程的世界吧!!!

1.创建线程的方法:

1.1通过继承Thread类,然后重写run()方法

1.2.实现Runnnable接口,重写run方法

1.3.二者区别

2.(通过jconsole观察进程里面的多线程情况)

2.1.首先我们要知道每一个线程都是独立的执行流

2.1.jconsole

3.Thread的一些其他方法

4.中断一个线程(interrupt)

4.1.手动设置标志位

4.2.Thread内置的标志位

5.线程等待(join方法)

6.线程状态

7.线程安全问题(最重要)

8.synchronized(原子性)


1.创建线程的方法:

线程实话说是操作系统的概念程序员想要操作线程肯定就需要操作系统提供给我们一些API,但是不同的操作系统提供的API又不同 (实话说这就让小编想起了这个)

=>  java就针对上述的系统API进行了封装(跨平台嘛,(^-^)V 我们java太厉害啦)

=> 我们程序员只需要了解这一套API就够啦~~

 java提供给我们的API就是Thread类,我们创建Thread对象就可以操作系统内部的线程啦

老规矩:学习一个类,先看他的构造方法

我们大概先学这几种,还有一种线程分组的,小编现在也不了解,后续再给大家介绍吧~~

Runnable表示一个以运行的任务而已,这个任务是交给线程执行还是其他用图我们不关心

1.1通过继承Thread类,然后重写run()方法

class MyThread extends Thread{@Overridepublic void run() {System.out.println("我创建的一个新线程"); //  这个run方法里面描述了我这个线程要干啥}
}

这是我们自己创建的线程但是一个java程序跑起来,还有一个跟随进程一起创建的线程,这个线程叫做主线程,这个主线程的入口方法是main方法。

这个run方法呢是我们创建的这个线程的入口方法!!!

一个程序跑起来,从哪个方法开始执行,哪个方法就是他的入口方法。

run方法是入口方法没有错,但是这个线程我们想要跑起来,肯定要启动呀

class MyThread extends Thread{@Overridepublic void run() {System.out.println("我创建的一个新线程");}
}
public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();t.start();  // 启动线程,start方法才是真正的调用系统的API ,// 创建一个线程然后这个线程通过run方法跑起来System.out.println("这就是主线程");}

1.2.实现Runnnable接口,重写run方法

class MyRunnable implements Runnable {@Overridepublic void run() {while (true) {System.out.println("我实现Runnable,重写run方法实现的任务");}}
}public class Test {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();while (true) {System.out.println("主线程");}}}

当然我们通过匿名内部类lambda表达式实现也完全没问题(上面我们继承Thread类重写run方法当然也可以使用匿名内部类的方式,但是lambda表达式不可以函数式接口定义(就是lambda表达式):一个接口有且只有一个抽象方法))

Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("通过匿名内部类实现");}});Thread t2 = new Thread(() -> {System.out.println("通过lambda表达式实现");});

1.3.二者区别

首先我们要明确创建线程的两个关键操作

1.首先要明确要执行的任务(就是想要通过这个线程干啥)

2.调用系统API创建出线程。

好,现在我们就可以明确知道他俩的区别了,耦合性不同

1.第一种方法(继承Thread的)把任务嵌套在了线程里面(后面想要修改任务,就要修改线程的源代码(大工程))

2.第二种方法:我要让线程执行其他任务,直接构造方法变成其他任务就好了。并且我这个任务又不是只给一个线程使用,其他线程想用的话直接传过去就行了。

总之:把任务分离出来,耦合性更低了,效率更高!!!

2.(通过jconsole观察进程里面的多线程情况

2.1.首先我们要知道每一个线程都是独立的执行流

就拿这个代码来说(但是这个代码打印的太快了不好观察,我们可以让他休眠一下,慢一点打印

class MyThread extends Thread{@Overridepublic void run() {System.out.println("我创建的一个新线程");}
}public class Test {public static void main(String[] args){Thread t = new MyThread();t.start(); while (true) {System.out.println("主线程");}}

休眠的方法:sleep方法是Thread类的静态方法(这个异常的处理很有讲究的,大家尽量不要犯这样的错误呀)

class MyThread extends Thread{@Overridepublic void run() {while(true){try {System.out.println("我创建的一个新线程");Thread.sleep(1000);//  sleep 这里就是一个类方法,我们直接调用就好了//  这里不能抛出异常,如果子类方法抛出额外的异常,调用者(可能只了解父类方法)可能不知道如何正确处理这些新的异常。//这里 父类的 run方法并没有抛出异常} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();t.start();while (true) {Thread.sleep(1000);System.out.println("主线程");}}

可以看到两个线程的正在交替打印日志,并且不是一种规律进行打印。

你俩都是休眠1000ms,休眠后并且你你俩谁先执行都不一定(随机的),这个取决于操作系统对于调度器的的具体实现。

通过并发执行,更加充分的的利用CPU资源

2.1.jconsole

jconsole(观察多线程属性的一种方式,还有IDEA的一种一个)

jdk里面的一个可执行程序,jdk的位置:

如何启动:

1.启动之前保证idea的程序已经跑起来了

2.有的兄弟需要管理员方式运行(正常情况不行的话)

=>

3.Thread的一些其他方法

    public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("创建的线程");},"主线程");System.out.println(t.getId());  //java 给这个线程分配的idSystem.out.println(t.getName()); // 我设置的线程名字  -> 主线程System.out.println(t.isDaemon()); // 后台线程? falseSystem.out.println(t.isAlive());// 线程是否存活  false 还没开始运行t.setDaemon(true);  // 设置t线程为后台线程//这四行一下就执行完了t.start();System.out.println(t.isAlive());  //true ,不过就存活这一下就结束了,其他线程都结束了//后台线程肯定结束了// 最后发现没打印创建的线程}

4.中断一个线程(interrupt)

java里面就是让一个线程快点结束(而不是直接让一个线程中间断掉(这样一个线程会导致残留数据,不太科学))

4.1.手动设置标志位

我们用一个isQuit作为标志位,让主线程5秒后修改isQuit的值

 public static  boolean isQuit = false;  // 类属性public static void main(String[] args) throws InterruptedException {//这里就涉及到了变量的捕获的语法,但是Thread t = new Thread(() ->{while(!isQuit){//我这里捕获到的其实是拷贝外部变量的一份System.out.println("我新创建的一个线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("线程工作完毕");},"t线程");t.start();Thread.sleep(5000); // 五秒之后设置isQuit = trueisQuit = true;}

给大家提一个问题,毕竟当时小编在写这个代码的时候也忘了

这里不涉及是涉及匿名内部类的变量捕获吗?为什么这里不报错呢?

答:这个时候isQuit是成员变量,此时lambda访问这个成员,就不再是变量捕获的语法了,变成了“内部类访问外部类”的语法,就没有final(实际上没有被修改过的也算)的限制了

另外:对于基本数据类型,捕获的是它们的值的副本(实际上就是复制了一份)

如果是局部变量的话就会报错,因为修改了

如果一个局部变量不能解决问题就可以考虑把这个局部变量换成成员变量

但是这样写还是不太舒服

1.还要我们自己创建

2.并且如果另一个线程正在sleep就不能够及时响应

java有没有已经设置好的标志位呢?当然!!!

4.2.Thread内置的标志位

t.interrupt的作用:

    public static void main(String[] args) {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("我新建的一个线程");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();   //  这里扔出一个异常,也仅仅只是打印出异常的位置信息而已,也没真正的处理什么}}System.out.println("我的循环即将终止");});t.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("让线程终止");t.interrupt();// 把线程内部的标志位设置为true}

我们发现线程确实还是在运行,一张图搞懂

还有一个静态方法,那大家都用的是一个标志位实在是不太科学(但是如果是你想让两个东西抵消好像还可以)(先记住吧)

5.线程等待(join方法)

线程等待:一个线程等待另一个线程结束再执行(控制线程结束的顺序)

    public static void main(String[] args) throws InterruptedException {  //线程阻塞抛出的异常Thread t =new Thread(() ->{while(true){System.out.println("我新建的一个线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();System.out.println("等待线程开始");t.join(5000);  // 让主线程等待5s,也会造成线程阻塞,也要抛出异常System.out.println("join 等待结束");}

   public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {System.out.println("我新建的一个线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}});long t1 = System.currentTimeMillis();t.start();t.join();  // 也会造成线程阻塞,也要抛出异常long t2 = System.currentTimeMillis(); // 调度,还有创建线程的开销System.out.println(t2 - t1);  //}

6.线程状态

    public static void main(String[] args) {for(Thread.State state : Thread.State.values()){System.out.println(state);}}

//观察状态public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {try {Thread.sleep(1000);System.out.println("11111111");} catch (InterruptedException e) {throw new RuntimeException(e);}});System.out.println(t.getState()); //  NEWt.start();System.out.println(t.getState()); //RUNNABLEThread.sleep(500);System.out.println(t.getState()); //TIMED_WAITING: 由于sleep这种固定时间的方式产生的阻塞System.out.println(t.getState());  // terminated,Thread对象还在,但是我创建的对象已经跑完了}

7.线程安全问题(最重要)

简述:

单个线程下执行可以,但是多个线程执行就出现bug,这种就是线程安全问题

(写出的代码和预想的结果不同就是bug!!!)

我们先看一个代码:两个线程同时修改一个静态成员变量

   public static int count;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for(int i = 0; i < 5000; i++){count++;}});Thread t2 = new Thread(() ->{for (int i = 0; i < 5000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}

竟然不是10000

但是我们修改一下代码:结果就是10000了

   public static int count;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for(int i = 0; i < 5000; i++){count++;}});Thread t2 = new Thread(() ->{for (int i = 0; i < 5000; i++) {count++;}});t1.start();t1.join();t2.start();t2.join();System.out.println(count);}

到底是什么原因呢?

count++这个操作其实是分成三步进行的(CPU通过三个指令来实现的)

这种也可能(所以说这排列组合是无数种)

归根到底:

这是因为线程的抢占式运行,两个线程不是往上累加而是独立运行。

如果这个count++变成一条指令(原子的),那么线程随便执行都没问题了

所有的都变成了样子,这个时候我们就想到给这个count++进行加锁,把他变成一条指令!!!

8.synchronized(原子性)

我们就需要使用synchronized关键字给一个代码块进行加锁

   public static int count;public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(() -> {for(int i = 0; i < 5000; i++){synchronized (locker){count++;}//synchronized,// 我们把要加锁的代码放在这个代码块里面就行了//我们如果对两个线程加相同的锁,就会造成"锁竞争/锁冲突“,毕竟我们就只有这一把锁,线程2必须等线程1解锁然后再加锁//    由于我们是对一个变量进行操作,我们还是尽量加同一把锁,两把锁,还是会并发执行//导致:那么一个线程对 count 的修改可能不会被另一个线程立即看到.}});Thread t2 = new Thread(() ->{for (int i = 0; i < 5000; i++) {synchronized (locker){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}

之后就变成这么执行了,变成了串行执行

t2线程由于锁的竞争(拿不到locker的监视器锁)就只能阻塞等待,一直等待到t1线程unlock之后才能获得这把锁,这样就避免了load,add,save的穿插执行。

如果我们两个线程分别给两个线程进行加锁,就还是会穿插执行。因为没有了锁竞争!!!

后面的线程安全问题后面再讲吧~~~(上一次看铠甲勇士还是在上一次~~~哈哈哈)

上述就是进程的"黑匣子":PCB如何记录着任务的一生

的全部内容了,线程的出现,我们的效率又得到了很大的提升~~~,但是管理和安全也是一个很大的问题。预知后事如何,请听下回分解~~~

能看到这里相信您一定对小编的文章有了一定的认可。

有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正~~

您的支持就是我最大的动力​​​!!!

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

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

相关文章

前端数据模拟 Mock.js 学习笔记(附带详细)

前端数据模拟 Mock.js 学习笔记 在前端开发过程中&#xff0c;数据模拟是一项至关重要的环节。当后端接口尚未完成或者需要独立进行前端开发与测试时&#xff0c;Mock.js 能发挥巨大作用&#xff0c;它可以模拟各种数据场景&#xff0c;助力前端开发高效进行。 一、Mock.js 的…

NoteGen是一款开源跨平台的 AI 笔记应用,专注于 recording 和 writing ,基于 Tauri 开发

一、软件介绍 文末提供程序和源码下载 NoteGen 是一款专注于记录和写作的跨平台 AI 笔记应用&#xff0c;基于 Tauri 开发。NoteGen 的核心理念是将记录、写作和 AI 结合使用&#xff0c;三者相辅相成。记录功能可以帮助用户快速捕捉和整理碎片化知识。整理功能是连接记录和写…

学习网络安全需要哪些基础?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 学习网络安全&#xff0c;对于想要进入IT行业的朋友们来说是一件非常重要的事情。尤其是在当今社会&#xff0c;互联网已经渗透到工作和生活的方方面面&#xff0…

系统安全阶段练习真题(高软44)

系列文章目录 系统安全阶段练习真题 文章目录 系列文章目录前言一、真题总结 前言 本节就是系统安全的阶段练习真题&#xff0c;带答案与解析。 一、真题 总结 就是高软笔记&#xff0c;大佬请略过&#xff01;

C++性能分析工具

C性能分析工具常用的三种。perf、gprof、pprof perf工具需要root权限&#xff0c;设置perf的suid位并不行&#xff0c;需要设置perf对应的内核参数。 perf使用&#xff1a; g -o example example.cpp -O2 # 运行程序并采样 sudo perf record -g ./example # 查看采样结果 sud…

基于PyTorch的深度学习5——神经网络工具箱

可以学习如下内容&#xff1a; • 介绍神经网络核心组件。 • 如何构建一个神经网络。 • 详细介绍如何构建一个神经网络。 • 如何使用nn模块中Module及functional。 • 如何选择优化器。 • 动态修改学习率参数。 5.1 核心组件 神经网络核心组件不多&#xff0c;把这些…

Spring Cloud之注册中心之Nacos负载均衡

目录 负载均衡 服务下线 权重配置 配置权重 解决办法 常见问题 同集群优先访问 给实例配置集群名称 开启Nacos负载均衡策略 负载均衡 ⽣产环境相对是⽐较恶劣的, 我们需要对服务的流量进⾏更加精细的控制. Nacos⽀持多种负载均衡策略, 包括权重, 同机房, 同地域, 同环…

音视频入门基础:RTP专题(16)——RTP封装音频时,音频的有效载荷结构

一、引言 《RFC 3640》和《RFC 6416》分别定义了两种对MPEG-4流的RTP封包方式&#xff0c;这两个文档都可以从RFC官网下载&#xff1a; RFC Editor 本文主要对《RFC 3640》中的音频打包方式进行简介。《RFC 3640》总共有43页&#xff0c;本文下面所说的“页数”是指在pdf阅读…

操作系统控制台-健康守护我们的系统

引言基本准备体验功能健康守护系统诊断 收获提升结语 引言 阿里云操作系统控制平台作为新一代云端服务器中枢平台&#xff0c;通过创新交互模式重构主机管理体验。操作系统控制台提供了一系列管理功能&#xff0c;包括运维监控、智能助手、扩展插件管理以及订阅服务等。用户可以…

ASP.NET Core 6 MVC 文件上传

概述 应用程序中的文件上传是一项功能&#xff0c;用户可以使用该功能将用户本地系统或网络上的文件上传到 Web 应用程序。Web 应用程序将处理该文件&#xff0c;然后根据需要对文件进行一些验证&#xff0c;最后根据要求将该文件存储在系统中配置的用于保存文件的存储中&#…

JVM之Arthas的dashboard命令以及CPU飙高场景

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

JSAR 基础 1.2.1 基础概念_空间小程序

JSAR 基础 1.2.1 基础概念_空间小程序 空间空间自由度可嵌入空间空间小程序 最新的技术进展表明&#xff0c;官网之前的文档准备废除了&#xff0c;基于xsml的开发将退出历史舞台&#xff0c;three.js和普通web结合的技术将成为主导。所以后续学习请移步three.js学习路径&#…

蓝桥杯嵌入式组第七届省赛题目解析+STM32G431RBT6实现源码

文章目录 1.题目解析1.1 分而治之&#xff0c;藕断丝连1.2 模块化思维导图1.3 模块解析1.3.1 KEY模块1.3.2 ADC模块1.3.3 IIC模块1.3.4 UART模块1.3.5 LCD模块1.3.6 LED模块1.3.7 TIM模块 2.源码3.第七届题目 前言&#xff1a;STM32G431RBT6实现嵌入式组第七届题目解析源码&…

Java之IO流

什么是IO流 存储和读取数据的解决方案 I&#xff1a;input:输入 O&#xff1a;output&#xff1a;输出 流&#xff1a;像水流一样传输数据 IO流的作用 用于读取数据&#xff08;本地文件&#xff0c;网络&#xff09; IO流的分类 流的方向&#xff1a; 输入流&#xff…

Python入门———条件、循环

目录 语句 顺序语句 条件语句 缩进和代码块 判断年份是否是闰年 空语句 pass 循环 while 循环 求5的阶乘&#xff1a; 求1&#xff01;2&#xff01;3&#xff01;4&#xff01;5&#xff01; for循环 打印1-10 打印2&#xff0c;4&#xff0c;6&#xff0c;8&#x…

JWT的学习

1、HTTP无状态及解决方案 HTTP一种是无状态的协议&#xff0c;每次请求都是一次独立的请求&#xff0c;一次交互之后就是陌生人。 以CSDN为例&#xff0c;先登录一次&#xff0c;然后浏览器退出&#xff0c;这个时候在进入CSDN&#xff0c;按理说服务器是不知道你已经登陆了&…

【接口负载】✈️整合 Resilience4j 指定接口负载,避免过载

目录 &#x1f44b;前言 &#x1f378;一、应用场景 &#x1f37b;二、 代码实现 &#x1f379;三、扩展 &#x1f378;四、章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;上次本地实操了下针对百万级数据量如何快速排序、指定条件获取等&#xff0c;文章内容包括&am…

CSS—网格布局Grid

网格布局grid 提供了带有行和列的基于网格的布局系统&#xff0c;无需使用浮动和定位。 当 HTML 元素的 display 属性设置为 grid 或 inline-grid 时&#xff0c;它就会成为网格容器。 更多布局模式可以参考之前的博客&#xff1a; ​​​​​​CSS—flex布局、过渡transit…

表格columns拼接两个后端返回的字段(以umi框架为例)

在用组件对前端项目进行开发时&#xff0c;我们会遇到以下情况&#xff1a;项目原型中有取值范围这个表字段&#xff0c;需要存放最小取值到最大取值。 而后端返回给我们的数据是返回了一个最小值和一个最大值&#xff0c; 在columns中我们需要对这两个字段进行拼接&#xff0…

Git系列之git tag和ReleaseMilestone

以下是关于 Git Tag、Release 和 Milestone 的深度融合内容&#xff0c;并补充了关于 Git Tag 的所有命令、详细解释和指令实例&#xff0c;条理清晰&#xff0c;结合实际使用场景和案例。 1. Git Tag 1.1 定义 • Tag 是 Git 中用于标记特定提交&#xff08;commit&#xf…