【Java并发编程一】八千字详解多线程
目录
多线程基础
1.线程和进程
线程是什么?
为啥要有线程?
进程和线程的区别?
Java 的线程 和 操作系统线程 的关系
使用jconsole观察线程
2.创建线程的多种方式
3.Thread类及其常见方法
Thread类的常见构造方法
Thread类的常见属性
Thread类的常见方法
4.线程的各种状态
5.使用多线程的风险(线程安全问题)
多线程基础
并发编程是目前很多大公司面试考核的重点内容,为什么并发编程那么重要呢?这还要从CPU的发展讲起,考量一块CPU性能高不高的重要一个因素就是CPU的计算能力,起初,为了提升CPU的计算能力,硬件厂家们尽可能的缩小其内部每个计算单元所占的体积,保证每块CPU上尽可能的装有更多的计算单元,但受限于工艺水平,硬件厂家们发现,当一块CPU内计算单元的数目越多时,它成为残次品的概率也会提高,相应成本也会提高,为了解决这一问题,硬件厂家们发明了现在常见的多核CPU,及一个CPU上存在多个核心。为了充分利用多核技术,逐渐发展起来了多进程和多线程。下面就让我来带大家深入理解多线程的奥秘吧。
1.线程和进程
线程是什么?
为啥要有线程?
- 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源.
- 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.
进程和线程的区别?
- 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
- 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
- 创建线程比创建进程更快.
- 销毁线程比销毁进程更快.
- 调度线程比调度进程更快.
Java 的线程 和 操作系统线程 的关系
使用jconsole观察线程
在jdk的bin文件夹下,Java为我们提供了一个工具jconsole.exe,启动这个工具我们便可以清晰的观察自己电脑内Java线程的启动和销毁了。
2.创建线程的多种方式
方法一:继承Thread来创建一个线程类。
//创建一个线程类
class MyThread extends Thread {@Overridepublic void run() {System.out.println("这里是线程运行的代码");}
}
MyThread t = new MyThread();//创建 MyThread 类的实例
t.start(); // 线程开始运行
方法二: 实现Runnable接口
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这里是线程运行的代码");}
}
//创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
Thread t = new Thread(new MyRunnable());
t.start(); // 线程开始运行
方法三:使用ExecutorService、Callable、Future实现有返回结果的多线程
import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;/**
* 有返回值的线程
*/
@SuppressWarnings("unchecked")
public class Test {
public static void main(String[] args) throws ExecutionException,InterruptedException {System.out.println("----程序开始运行----");Date date1 = new Date();int taskSize = 5;// 创建一个线程池ExecutorService pool = Executors.newFixedThreadPool(taskSize);// 创建多个有返回值的任务List<Future> list = new ArrayList<Future>();for (int i = 0; i < taskSize; i++) {Callable c = new MyCallable(i + " ");// 执行任务并获取Future对象Future f = pool.submit(c);// System.out.println(">>>" + f.get().toString());list.add(f);}// 关闭线程池pool.shutdown();// 获取所有并发任务的运行结果for (Future f : list) {// 从Future对象上获取任务的返回值,并输出到控制台System.out.println(">>>" + f.get().toString());}Date date2 = new Date();System.out.println("----程序结束运行----,程序运行时间【"+ (date2.getTime() - date1.getTime()) + "毫秒】");
}
}class MyCallable implements Callable<Object> {
private String taskNum;MyCallable(String taskNum) {this.taskNum = taskNum;
}public Object call() throws Exception {System.out.println(">>>" + taskNum + "任务启动");Date dateTmp1 = new Date();Thread.sleep(1000);Date dateTmp2 = new Date();long time = dateTmp2.getTime() - dateTmp1.getTime();System.out.println(">>>" + taskNum + "任务终止");return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}
tips:
上述的方法一,方法二均可以通过匿名内部类的方式创建线程,还可用lambda表达式来简化
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {@Overridepublic void run() {System.out.println("使用匿名类创建 Thread 子类对象");}
};
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("使用匿名类创建 Runnable 子类对象");}
});
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {System.out.println("使用匿名类创建 Thread 子类对象");
});
3.Thread类及其常见方法
Thread类的常见构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
【了解】 Thread(ThreadGroup group, Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
Thread类的常见属性
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况,下面我们会进一步说明
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,即简单的理解,为 run 方法是否运行结束了
- 线程的中断问题,下面我们进一步说明
Thread类的常见方法
(1).线程启动(start()方法)
前面我们知道覆写run方法来创建一个线程对象,但run方法只是为了给线程启动提供一个要做的事情清单,不能通过调用run方法来使线程启动。
调用start方法,可以真正在操作系统的底层创建一个线程。
(2).线程中断(interrupt()方法)
interrupt()、interrupted() 和 isInterrupted() 方法是 Java 中用于线程中断的相关方法,它们有着不同的功能和用法。下面我将逐一解释它们的区别。
我们把run方法运行结束叫做线程的中断,通常情况下我们为了保证线程的存在,会在run方法内部自定义设置一个循环条件等于true,让循环能一直存在,我们把这个条件叫做标记值,当标记值被改为false时,循环结束,run方法也会执行完毕。当run方法内的代码运行完毕之后,内核中的线程就会被摧毁,称为线程中断。除了自定义标记值之外,Thread内部还提供了一个boolean类型的变量可以作为标记值(isInterrupted),
- 清除中断标志:将 isInterrupted 的值设置为 false
- 设置中断标志:将 isInterrupted 的值 设置为 true
3.2.1 interrupt()
当调用线程的 interrupt() 方法时,如果线程处于阻塞状态,就会抛出 InterruptedException 异常并清除中断标志,接着结束睡眠。
如果线程未处于阻塞状态,仅仅是设置中断标志,并不是真正的中断线程。线程的中断由线程决定,线程可以通过检查中断标志来决定是否中断自己的执行。
public class MyThread extends Thread {@Overridepublic void run() {while (!Thread.currentThread().isInterrupted()) {// 线程任务逻辑// ...}}
}public static void main(String[] args) {MyThread thread = new MyThread();thread.start();// 等待一段时间后中断线程try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt();
}
3.2.2 interrupted()
interrupted() 方法是一个静态方法,用于判断当前线程是否被中断,并返回中断状态,而且在判断中断状态后,还会自动清除中断标志。
- 如果线程没有被中断,则返回 false;
- 如果线程被中断,则返回 true;
public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.interrupted());}}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");thread.start();thread.interrupt();}
}
3.2.3 isInterrupted()
isInterrupted() 用于检查当前线程是否被中断,并且不会清除线程的中断状态。
- 当线程被中断时,返回 true。
- 当线程没有被中断时,返回 false。
public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().isInterrupted());}}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");thread.start();thread.interrupt();}
}
(3).线程等待(join()方法)
java中线程是并发执行的,线程的调度是抢占式的,所以操作系统对于线程调用的顺序的不知道的,我们无法判断哪个线程先结束,为了能够控制线程的结束顺序,因此 java 提供了 join()方法。join方法内可以添加long类型参数,译为等待线程结束,最多等多少毫秒。
在 Java 中,join() 方法是 Thread 类的一个方法,它允许一个线程等待另一个线程执行完毕再执行接下来的步骤。当一个线程调用另一个线程的 join() 方法时,调用线程将被阻塞。
public class JoinExample {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {try {// 模拟线程执行耗时的操作Thread.sleep(2000);System.out.println("子线程执行完毕");} catch (InterruptedException e) {e.printStackTrace();}});thread.start(); // 启动子线程thread.join(); // 主线程等待子线程执行完毕System.out.println("主线程继续执行");}
}
(4).获取线程引用(currentThread()方法)
public static Thread currentThread(); | 返回当前线程对象的引用 |
public class ThreadDemo {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}
(5).线程休眠(sleep()方法)
让线程休眠一段时间,不去参与CPU的竞争,与阻塞状态不同,需要注意的是,因为线程的调度室不可控的,所以,这个方法只能保证实际休眠时间>=参数的设置的休眠时间。
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}
4.线程的各种状态
线程的状态是一个枚举类型 Thread.State,我们可以通过这段代码得到线程的状态:
public class ThreadState {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}
Java中线程的状态主要有以下几种:
(1)新建状态(NEW) Thread对象创建了。但是还没有调用start,操作系统内核中的线程还没创建
(2)终止状态(TERMINATED) run方法执行完毕,内核中的线程已经销毁
(3)可运行状态(RUNNABLE) 线程就绪(正在cpu上执行)
(4)超时等待状态(TIMED_WAITING) 由sleep等带有有时间的方法进入的阻塞
(5)等待状态(WAITING) 不带时间的阻塞,线程在等待其他线程的特定操作,如wait / join
(6)阻塞状态(BLOCKED) 由于锁竞争产生的阻塞
5.使用多线程的风险(线程安全问题)
虽然我们现在使用的CPU拥有多个核心,在正常使用计算机时,一个核心上仍然会同时运行多个线程,怎样保持多个线程同时运行呢,其实是通过高频切换来完成的,即一个线程在CPU上运行一会,再切换为另一个线程运行,因为中间切换的时间很短,且CPU执行速率非常快,在宏观层面我们就认为是多个线程在同时运行。如果同时运行的这几个线程在操作同一件事,且这件事不具有原子性就很容易发生线程安全问题。
当两个线程同时执行n++操作时,有可能其中一个线程刚执行完第一步,CPU就切换线程了,此时第二个线程执行n++的操作执行完毕重新写回内存,这时CPU重新切回第一个线程,第二个线程继续之前的执行第二步操作,因为两个线程都是在同一个n的基础上进行了+1操作,最后写会内存中的n只加了一次1,但我们执行了两次n++内存中的正确结果应该是n要加两次1,就会出现bug,想像一下,如果这种情况出现在银行存钱的时候,你和你家人同存钱,却只显示存进去了一份,这会是一件多么严重的bug,为了解决这一问题,Java也给我们提供了很多方法,我会在下篇文章详细介绍如何避免发生线程安全问题。
❤️😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍
🍔我是小皮侠,谢谢大家都能看到这里!!
🦚主页已更新Java基础内容,数据结构基础,数据库,算法
🚕未来会更新Java项目,SpringBoot,Redis以及各种Java路线会用到的技术。
🎃求点赞!求收藏!求评论!求关注!
🤷♀️谢谢大家!!!!!!!!!
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2upjellgk3eow
相关文章:

【Java并发编程一】八千字详解多线程
目录 多线程基础 1.线程和进程 线程是什么? 为啥要有线程? 进程和线程的区别? Java 的线程 和 操作系统线程 的关系 使用jconsole观察线程 2.创建线程的多种方式 3.Thread类及其常见方法 Thread类的常见构造方法 Thread类的常见属性…...

CentOS 8FTP服务器
FTP(文件传输协议)是一种客户端-服务器网络协议,允许用户在远程计算机之间传输文件。这里有很多可用于Linux的开源FTP服务软件,最流行最常用的FTP服务软件有 PureFTPd, ProFTPD, 和 vsftpd。在本教程中,我们将在CentOS…...

C++ | Leetcode C++题解之第385题迷你语法分析器
题目: 题解: class Solution { public:NestedInteger deserialize(string s) {if (s[0] ! [) {return NestedInteger(stoi(s));}stack<NestedInteger> st;int num 0;bool negative false;for (int i 0; i < s.size(); i) {char c s[i];if …...

【软件设计师真题】第一大题---数据流图设计
解答数据流图的题目关键在于细心。 考试时一定要仔细阅读题目说明和给出的流程图。另外,解题时要懂得将说明和流程图进行对照,将父图和子图进行对照,切忌按照常识来猜测。同时应按照一定顺序考虑问题,以防遗漏,比如可以…...

系统架构的发展历程之模块化与组件化
模块化开发方法 模块化开发方法是指把一个待开发的软件分解成若干个小的而且简单的部分,采用对复杂事物分而治之的经典原则。模块化开发方法涉及的主要问题是模块设计的规则,即系统如何分解成模块。而每一模块都可独立开发与测试,最后再组装…...

基因组学中的深度学习
----/ START /---- 基因组学其实是一门将数据驱动作为主要研究手段的学科,机器学习方法和统计学方法在基因组学中的应用一直都比较广泛。 不过现在多组学数据进一步激增——这个从目前逐渐增多的各类大规模人群基因组项目上可以看出来,这其实带来了新的挑…...

解决老师询问最高分数问题的编程方案
解决老师询问最高分数问题的编程方案 问题分析数据结构选择:线段树线段树的基本操作伪代码伪代码:构建线段树伪代码:更新操作伪代码:查询操作C语言实现代码详细解释在日常教学中,老师经常需要查询某一群学生中的最高分数,并有时会更新某位同学的成绩。为了实现这一功能,…...

com.baomidou.mybatisplus.annotation.DbType 无法引入
com.baomidou.mybatisplus.annotation.DbType 无法引入爆红 解决 解决 ❤️ 3.4.1 是mybatis-plus版本,根据实际的配置→版本一致 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-annotation</artifactId>&…...

从零开始学习JVM(七)- StringTable字符串常量池
1 概述 String应该是Java使用最多的类吧,很少有Java程序没有使用到String的。在Java中创建对象是一件挺耗费性能的事,而且我们又经常使用相同的String对象,那么创建这些相同的对象不是白白浪费性能吗。所以就有了StringTable这一特殊的存在&…...

数据库课程设计mysql
进行 MySQL 数据库课程设计通常包括以下几个步骤,从需求分析到数据库设计和实现。以下是一个常见的流程及要点: 1. 需求分析 首先,明确系统的功能需求。这包括用户需求、业务流程、功能模块等。你需要与相关人员(比如老师、同学…...

AI学习指南深度学习篇-带动量的随机梯度下降法的基本原理
AI学习指南深度学习篇——带动量的随机梯度下降法的基本原理 引言 在深度学习中,优化算法被广泛应用于训练神经网络模型。随机梯度下降法(SGD)是最常用的优化算法之一,但单独使用SGD在收敛速度和稳定性方面存在一些问题。为了应…...

点餐小程序实战教程03创建应用
目录 1 创建应用2 第一部分侧边栏3 第二部分页面功能区4 第三部分大纲树5 第四部分代码区6 第五部分模式切换7 第六部分编辑区域8 第七部分组件区域9 第八部分,发布区域10 第九部分开发调试和高阶配置总结 上一篇我们介绍了如何实现后端API,介绍了登录验…...

鸿蒙自动化发布测试版本app
创建API客户端 API客户端是AppGallery Connect用于管理用户访问AppGallery Connect API的身份凭据,您可以给不同角色创建不同的API客户端,使不同角色可以访问对应权限的AppGallery Connect API。在访问某个API前,必须创建有权访问该API的API…...

力扣9.7
115.不同的子序列 题目 给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 109 7 取模。 数据范围 1 < s.length, t.length < 1000s 和 t 由英文字母组成 分析 令dp[i][j]为s的前i个字符构成的子序列中为t的前j…...

GPU 带宽功耗优化
移动端GPU 的内存结构: 先简述移动端内存cache结构;上图的UMA结构 on-Chip memory 包括了 L1、L2 cache,非常关键的移动端的 Tiles 也是保存在 on-chip上还包括寄存器文件:提供给每个核心使用的极高速存储。 共享内存(…...

Linux Centos 7网络配置
本步骤基于Centos 7,使用的虚拟机是VMware Workstation Pro,最终可实现虚拟机与外网互通。如为其他发行版本的linux,可能会有差异。 1、检查外网访问状态 ping www.baidu.com 2、查看网卡配置信息 ip addr 3、配置网卡 cd /etc/sysconfig…...

第三天旅游线路规划
第三天:从贾登峪到禾木风景区,晚上住宿贾登峪; 从贾登峪到禾木风景区入口: 1、行程安排 根据上面的耗时情况,规划一天的行程安排如下: 1)早上9:00起床,吃完早饭&#…...

C++第四十七弹---深入理解异常机制:try, catch, throw全面解析
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】 目录 1.C语言传统的处理错误的方式 2.C异常概念 3. 异常的使用 3.1 异常的抛出和捕获 3.2 异常的重新抛出 3.3 异常安全 3.4 异常规范 4.自定义…...

go 和 java 技术选型思考
背景: go和java我这边自身都在使用,感受比较深,java使用了有7年多,go也就是今年开始的,公司需要所以就学了使用,发现这两个语言都很好,需要根据场景选择,我写下我这边的看法。 关于…...

传统CV算法——边缘算子与图像金字塔算法介绍
边缘算子 图像梯度算子 - Sobel Sobel算子是一种用于边缘检测的图像梯度算子,它通过计算图像亮度的空间梯度来突出显示图像中的边缘。Sobel算子主要识别图像中亮度变化快的区域,这些区域通常对应于边缘。它是通过对图像进行水平和垂直方向的差分运算来…...

图像去噪算法性能比较与分析
在数字图像处理领域,去噪是一个重要且常见的任务。本文将介绍一种实验,通过MATLAB实现多种去噪算法,并比较它们的性能。实验中使用了包括中值滤波(MF)、自适应加权中值滤波(ACWMF)、差分同态算法…...

Vision Transformer(ViT)模型原理及PyTorch逐行实现
Vision Transformer(ViT)模型原理及PyTorch逐行实现 一、TRM模型结构 1.Encoder Position Embedding 注入位置信息Multi-head Self-attention 对各个位置的embedding融合(空间融合)LayerNorm & ResidualFeedforward Neural Network 对每个位置上单…...

828华为云征文 | Flexus X实例CPU、内存及磁盘性能实测与分析
引言 随着云计算的普及,企业对于云资源的需求日益增加,而选择一款性能强劲、稳定性高的云实例成为了关键。华为云Flexus X实例作为华为云最新推出的高性能实例,旨在为用户提供更强的计算能力和更高的网络带宽支持。最近华为云828 B2B企业节正…...

FreeRTOS学习笔记(六)队列
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、队列的基本内容1.1 队列的引入1.2 FreeRTOS 队列的功能与作用1.3 队列的结构体1.4 队列的使用流程 二、相关API详解2.1 xQueueCreate2.2 xQueueSend2.3 xQu…...

【Python篇】PyQt5 超详细教程——由入门到精通(中篇一)
文章目录 PyQt5入门级超详细教程前言第4部分:事件处理与信号槽机制4.1 什么是信号与槽?4.2 信号与槽的基本用法4.3 信号与槽的基础示例代码详解: 4.4 处理不同的信号代码详解: 4.5 自定义信号与槽代码详解: 4.6 信号槽…...

LinuxQt下的一些坑之一
我们在使用Qt开发时,经常会遇到Windows上应用正常,但到Linux嵌入式下就会出现莫名奇妙的问题。这篇文章就举例分析下: 1.QPushButton按钮外侧虚线框问题 Windows下QPushButton按钮设置样式正常,但到了Linux下就会有一个虚线边框。…...

Statement batch
我们可以看到 Statement 和 PreparedStatement 为我们提供的批次执行 sql 操作 JDBC 引入上述 batch 功能的主要目的,是加快对客户端SQL的执行和响应速度,并进而提高数据库整体并发度,而 jdbc batch 能够提高对客户端SQL的执行和响应速度,其…...

PPP 、PPPoE 浅析和配置示例
一、名词: PPP: Point to Point Protocol 点到点协议 LCP:Link Control Protocol 链路控制协议 NCP:Network Control Protocol 网络控制协议,对于上层协议的支持,N 可以为IPv4、IPv6…...

【Python机器学习】词向量推理——词向量
目录 面向向量的推理 使用词向量的更多原因 如何计算Word2vec表示 skip-gram方法 什么是softmax 神经网络如何学习向量表示 用线性代数检索词向量 连续词袋方法 skip-gram和CBOW:什么时候用哪种方法 word2vec计算技巧 高频2-gram 高频词条降采样 负采样…...

Python 语法糖:让编程更简单(续二)
Python 语法糖:让编程更简单(续) 10. Type hints Type hints 是 Python 中的一种语法糖,用于指定函数或变量的类型。例如: def greet(name: str) -> None:print(f"Hello, {name}!")这段代码将定义一个…...