【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算子主要识别图像中亮度变化快的区域,这些区域通常对应于边缘。它是通过对图像进行水平和垂直方向的差分运算来…...
本地柴油发电机组排行2023年最新榜单
柴油发电机是通过燃烧柴油驱动发动机,进而发电的设备,广泛应用于电力中断或无电网地区。1. 柴油发电机的核心工作原理是什么?柴油发电机是一种将化学能转化为电能的设备,其核心是柴油发动机与交流发电机的组合。当柴油在发动机内燃…...
孤舟笔记 互联网常用框架篇二 Dubbo服务请求失败怎么处理?集群容错策略你用过几种
文章目录先说结论Failover:换家店试试Failfast:不行就算了Failsafe:忘了这事Failback:回头再说Forking:同时点几家Broadcast:通知所有人怎么选择回答技巧与点评加分回答面试官点评个人网站分布式系统中&…...
如何快速掌握MoveIt2:面向ROS 2开发者的工业机器人运动规划完整指南
如何快速掌握MoveIt2:面向ROS 2开发者的工业机器人运动规划完整指南 【免费下载链接】moveit2 :robot: MoveIt for ROS 2 项目地址: https://gitcode.com/gh_mirrors/mo/moveit2 想要为你的机器人实现智能运动规划吗?MoveIt2作为ROS 2生态中最强大…...
二十六.签名与脚本(1)--脚本介绍
1.区块链脚本介绍在之前的章节中,我们了解了签名与验证相关,但是btc的交易数据,签名和验证,不是单纯的,还有脚本深度参与其中。我们从开始来:bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletT…...
【云雾效果商业级交付标准】:基于Adobe Sensei图像雾度分析报告(N=1,247张MJ生成图),锁定雾浓度≤0.38的7个关键阈值参数
更多请点击: https://intelliparadigm.com 第一章:云雾效果商业级交付标准的定义与行业意义 云雾效果在现代数字体验中已超越视觉装饰范畴,成为空间感知建模、沉浸式交互与品牌情绪传达的核心媒介。商业级交付标准并非仅关注“是否可见雾气”…...
【Veo 2提示词SOP白皮书】:从模糊意图到像素级输出的8步标准化工作流(附NASA级测试用例库)
更多请点击: https://intelliparadigm.com 第一章:Veo 2提示词工程的本质与范式跃迁 Veo 2并非单纯升级的视频生成模型,而是一次提示词工程范式的根本性重构——它将传统“指令式提示”(prompt-as-command)转向“意图…...
对比按量计费与Token Plan套餐的实际成本差异
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 对比按量计费与Token Plan套餐的实际成本差异 在构建和运营基于大模型的应用时,成本控制是一个核心的工程考量。Taotok…...
深度解析:JetBrains IDE试用期重置机制的技术实现
深度解析:JetBrains IDE试用期重置机制的技术实现 【免费下载链接】ide-eval-resetter 项目地址: https://gitcode.com/gh_mirrors/id/ide-eval-resetter 在软件开发工作流中,JetBrains IDE试用期管理是一个常见的技术挑战,尤其是在多…...
保姆级教程:手把手教你搞定ESXi 6.7安装前的BIOS设置(VT-x/VT-d/AES全开)
从零开始:ESXi 6.7安装前的BIOS设置终极指南当你第一次接触企业级虚拟化平台时,那种既兴奋又忐忑的心情我完全理解。作为过来人,我记得自己第一次在Dell PowerEdge服务器上安装ESXi时,光是搞清楚BIOS里那些晦涩的选项就花了整整一…...
CMSIS-DAP调试器原理与应用:以Elektor mbed interface为例
1. 项目概述:Elektor mbed interface [150554] 是什么?如果你玩过ARM Cortex-M系列的单片机,尤其是NXP LPC800系列,那你可能对“CMSIS-DAP”这个调试器标准不陌生。它是由ARM官方推出的一个开源调试接口标准,最大的好处…...
