【JavaEE】【多线程】Thread类讲解

目录

  • Thread构造方法
  • Thread 的常见属性
  • 创建一个线程
  • 获取当前线程引用
  • 终止一个线程
    • 使用标志位
    • 使用自带的标志位
  • 等待一个线程
  • 线程休眠
  • 线程状态
  • 线程安全
    • 线程不安全原因总结
    • 解决由先前线程不安全问题例子

Thread构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名(当前线程名)
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即为线程组

Thread 的常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

解释:

  • ID 是线程的唯一标识,不同线程不会重复,但是这里的id是Java给的id,不是前面PCB中说的id。
  • 名称在各种调试工具用到,前面构造方法给的名称就是这个。
  • 状态表示线程当前所处的一个情况。
  • 优先级高的线程理论上来说更容易被调度到,但是这个是系统微观程度上的,很难感知到。
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程(前台线程)结束后,才会结束运行,而后台线程不影响Java进程的结束,可以在start()调用前使用setDaemon(true)来设置线程为后台线程。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了

创建一个线程

在前一篇文章中就介绍了相关操作,在这简单提一下一定要使用线程变量名.start();创建一个新线程,start()方法是Java提供的API来调用系统中创建线程的方法。而run()方法是这个线程要干的事情,在线程创建好之后自动就会调用。
每个线程对象只能start一次

获取当前线程引用

方法说明
public static Thread currentThread();返回当前线程对象的引用

是静态方法直接使用Thread.currentThread();就可以获取到当前的线程引用。

终止一个线程

在Java中终止一个线程的思路就是让线程中的run()方法尽快结束。

使用标志位

由于线程迟迟不结束大多是因为里面有循环语句,我们就可以使用一个成员变量来控制循环的结束。
不能使用局部变量定义在main方法内,因为虽然lambda表达式可以捕获上层变量,但是这个变量不可以进行修改。

public class Demo {private static boolean isQuit = false;public static void main(String[] args) {Thread thread = new Thread(() ->{while(isQuit) {//具体操作  }});thread.start();isQuit = true;}
}

使用自带的标志位

方法说明
public void interrupt()中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位
public static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位,不建议使用,静态方法为所有线程共用的
public boolean isInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位

Java中自带了标志位来标志是否结束循环。先使用Thread.currentThread()获取到当前线程,在.isInterrupted()获取标志位。然后再主进程中调用interrupte()方法来将标志位值修改为true。

public class Demo {public static void main(String[] args) {Thread thread = new Thread(() ->{while (!Thread.currentThread().isInterrupted()) {//操作}});thread.start();thread.interrupt();}
}

但是如果在线程中有捕获InterruptedException异常的语句,那么会在调用interrupte()同时捕获到该异常,并且消除标志位。
此时我们就可以在catch语句中自己选择是将线程结束还是进行其它操作。

public class Demo {public static void main(String[] args) {Thread thread = new Thread(() ->{while (!Thread.currentThread().isInterrupted()) {try {Thread.sleep(1000);} catch (InterruptedException e) {//1.不操作继续执行线程e.printStackTrace();//2.结束线程break;//3.进行其它操作}}});thread.start();thread.interrupt();}
}

等待一个线程

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)等待线程结束,最多等 millis 毫秒,但可以更高精度

在主线程中调用线程对象.join();就是等待线程对象执行完再执行主线程。
调用细节:

  • 调用线程对象.join();就会让该线程执行完才继续执行外面的线程,如果线程对象对应的线程一直不结束那么外面的线程就会一直等(死等)
  • 调用线程对象.join(long millis);就会在该线程执行millis毫秒后执行外面的线程。
  • 如果遇到调用join前线程已经结束,外面的线程不会陷入等待。

如下代码执行结果就是先打印5个thread线程,最后在打印main线程:

public class Demo6 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {for(int i = 0; i < 5; i++) {System.out.println("thread线程");}});       thread。start();thread.join();System.out.println("main线程");}
}

线程休眠

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException可以更高精度的休眠

在系统让线程休眠sleep中的参数毫秒后,线程会被唤醒从阻塞状态变成就绪状态,但不会马上执行,涉及到调度开销。所以实际使用的时间是大于sleep中的参数的。
并且在Windows和Linux系统上达到毫秒级误差。

线程状态

在操作系统里面进程和线程最重要的状态就是:就绪状态和阻塞状态。
在Java中又给线程又给线程赋予了一些其他状态。
线程的状态是一个枚举类型 Thread.State。

状态说明
newThread对象已经创建,但是start方法没有调用
terminatedThread对象还在,但是内核中线程已将结束了
Runnable就绪状态,线程已经在CPU上执行或者在CPU上等待执行
timed_waiting由于sleep这种固定时间产生的阻塞
waiting由于wait这种不固定时间产生的阻塞
blocked由于锁竞争产生的阻塞

线程安全

线程安全的简单说法就是符不符合预期:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

例如以下代码:
我们的预期结果是10000,但是其实每次的结果都是不一样的,这种就是线程不安全。

public class Demo {private static int ret;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {ret++;}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {ret++;}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(ret);;}
}

就以上诉代码例子来讲解出现线程不安全的原因。

在CPU上实现自增操作主要有三步:

  1. 将数据给到CPU的寄存器中;
  2. 数据在寄存器中加1;
  3. 将数据返回到内存中。

就以一个thread1和一个thread2来说,每个线程都进行这三步操作,但是线程在CPU上又是随机调用的,这就相当于有六个位置随机坐,相当于排列组合的A66,当数据作为不同线程的开始值进入寄存器时就相当于两次自增只执行了一次。

但是线程调用就更加复杂了,线程数量不一样,顺序不一样,这就相当于有无数种可能了,所以结果是不可控的,就导致了线程不安全的情况。

线程不安全原因总结

在介绍线程不安全原因之前先介绍一个概念:原子性。

原子性:简单来讲就是执行一段代码连续执行完不被其他线程干扰。举个例子:

我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。

那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。

有时也把这个现象叫做同步互斥,表示操作是互相排斥的。

原因总结:

  • 操作系统调度线程是随机的(抢占式执行);
  • 多个线程对同一个变量进行修改;
  • 修改操作不是原子性的;
  • 内存可见性问题;
  • 指令重排序问题。

解决由先前线程不安全问题例子

要解决就要从原因入手:

  • 操作系统随机调度是操作系统带来的解决不了;
  • 多个线程对一个变量修改,有些可以规避,但有些根据需求无法规避。
  • 将操作改为原子性,可以通过synchronized关键字 加锁操作来实现。

语法:

synchronized(变量){
//修改操作
}

()括号内的变量不重要,作用是区分加锁对象是否一样,如果对同一个对象加锁,那么两个操作就会产生“blocked”锁竞争阻塞问题,后一个线程就会等到前一个线程解锁再执行。
进入左大括号 ‘{’ 就是加锁,出了右大括号 ‘}’ 就是解锁。

对上诉代码进行如下修改,就会出现预期结果10000:

public class Demo7 {private static int ret;public static void main(String[] args) throws InterruptedException {Object block = new Object();Thread thread1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (block){ret++;}}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (block){ret++;}}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(ret);;}
}

synchronized还可以修饰方法(静态方法也行)。

  • synchronized修饰实例方法:
class Counter{public int ret;public void increase1() {synchronized (this) {ret++;}}//简化版本synchronized public void increase2() {ret++;}
}
  • synchronized修饰静态方法:相当于修饰这个类
class Counter{private static int ret2;public static void increase3() {synchronized (Counter.class) {ret2++;}}//简化版本synchronized public static void increase4() {ret2++;}
}

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

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

相关文章

论文速读:基于渐进式转移的无监督域自适应舰船检测

这篇文章的标题是《Unsupervised Domain Adaptation Based on Progressive Transfer for Ship Detection: From Optical to SAR Images》基于渐进式转移的无监督域自适应舰船检测:从光学图像到SAR图像&#xff0c;作者是Yu Shi等人。文章发表在IEEE Transactions on Geoscience…

pg if条件语句

1.语法&#xff1a; 2.区别 IF 语句&#xff1a; 只能在 PL/pgSQL 中使用&#xff0c;不适合在直接的 SQL 查询中使用。没有返回值&#xff0c;仅仅是控制逻辑流程。适合用在存储过程、函数和触发器中。 CASE 语句&#xff08;在 PL/pgSQL 中&#xff09;&#xff1a; 可以在 P…

Comfyui segmentAnythingUltra V2报错

&#x1f385;问题表现及解决方案 Comfyui segmentAnythingUltra V2报错&#xff0c;找不到VITMatte模型&#xff0c;这个报错报的比较模糊&#xff0c;所以花了一点时间找模型。 简单来说&#xff0c;到huggingface上&#xff1a; https://huggingface.co/hustvl/vitmatte-s…

2024 Mysql基础与进阶操作系列之MySQL触发器详解(20)作者——LJS[你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽]

欢迎各位彦祖与热巴畅游本人专栏与博客 你的三连是我最大的动力 以下图片仅代表专栏特色 [点击箭头指向的专栏名即可闪现] 专栏跑道一 ➡️ MYSQL REDIS Advance operation 专栏跑道二➡️ 24 Network Security -LJS ​ ​ ​ 专栏跑道三 ➡️HCIP&#xff1b;H3C-SE;CCIP——…

Redis接口访问优化

说明&#xff1a;之前写过一篇使用Redis接口访问的博客&#xff0c;如下。最近有相关需求&#xff0c;把代码拿出来后&#xff0c;做了一些优化&#xff0c;挺有意思的&#xff0c;本文介绍在原基础上 使用Redis实现接口防抖 优化 总的来说&#xff0c;这次使用Redis实现接口…

操作系统 | 学习笔记 | 王道 | 4.1 文件系统基础

4.文件管理 4.1 文件系统基础 4.1.1 文件的基本概念 定义 文件是以计算机硬盘为载体的存储在计算机上的信息集合&#xff0c;在用户进行的输入、输出中&#xff0c;以文件位基本单位。 文件管理系统是实现的文件的访问、修改和保存&#xff0c;对文件维护管理的系统。 文件的…

2024重生之回溯数据结构与算法系列学习(11)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】

欢迎各位彦祖与热巴畅游本人专栏与博客 你的三连是我最大的动力 以下图片仅代表专栏特色 [点击箭头指向的专栏名即可闪现] 专栏跑道一 ➡️ MYSQL REDIS Advance operation 专栏跑道二➡️ 24 Network Security -LJS ​ ​ ​ 专栏跑道三 ➡️HCIP&#xff1b;H3C-SE;CCIP——…

APP自动化搭建与应用

APP自动化环境搭建 用于做APP端UI自动化&#xff0c;adb连接手机设备。 需要的工具java编辑器&#xff1a;jdk、Android-sdk软件开发工具组、appium的python客户端、nodes.js、夜神模拟器、apk包、uiautomatorviewer 第一步&#xff1a;安装sdk&#xff0c;里面包含建立工具bu…

一、机器学习算法与实践_06迭代法和KMeans、线性回归、逻辑回归算法笔记

0 迭代法 迭代法不仅是机器学习、深度学习的核心&#xff0c;也是整个人工智能领域的重要概念&#xff0c;其对于算法的设计和实现至关重要 0.1 适合场景 对于不能一次搞定的问题&#xff0c;将其分成多步来解决&#xff0c;逐步逼近解决方案 0.2 典型应用 KMeans 聚类算法…

9-贪心算法

PDF文档下载&#xff1a;LeetCode-贪心算法-java 参考&#xff1a;代码随想录 题目分类大纲如下&#xff1a; 贪心算法理论基础 什么是贪心&#xff1f; 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 贪心的套路&#xff08;什么时候用贪心&#xff…

计算机网络——http和web

无状态服务器——不维护客户端 怎么变成有状态连接 所以此时本地建立代理—— 若本地缓存了——但是服务器变了——怎么办&#xff1f;

Pikachu-File Inclusion-远程文件包含

远程文件包含漏洞 是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的&#xff0c;因此漏洞一旦存在&#xff0c;危害性会很大。但远程文件包含漏洞的利用条件较为苛刻&#xff1b;因此&#xff0c;在web应用系统的功能设计上尽量不要让前端用户直接传变…

用java编写飞机大战

游戏界面使用JFrame和JPanel构建。背景图通过BG类绘制。英雄机和敌机在界面上显示并移动。子弹从英雄机发射并在屏幕上移动。游戏有四种状态&#xff1a;READY、RUNNING、PAUSE、GAMEOVER。状态通过鼠标点击进行切换&#xff1a;点击开始游戏&#xff08;从READY变为RUNNING&am…

CSS | 响应式布局之媒体查询(media-query)详解

media type(媒体类型)是CSS 2中的一个非常有用的属性&#xff0c;通过media type我们可以对不同的设备指定特定的样式&#xff0c;从而实现更丰富的界面。media query(媒体查询)是对media type的一种增强&#xff0c;是CSS 3的重要内容之一。随着移动互联网的发展&#xff0c;m…

reactNative本地调试localhost踩坑

本地调试请求localhost的时候 1.要和电脑处在同一局域网下面&#xff08;同一个wifi&#xff09; 2.把baseURL的localhost改成命令行中ipconfig查询到的IPv4 地址 . . . . . . . . . . . . : &#xff08;例如&#xff09;192.168.1.103 如果报错Net Work Error&#xff0c;可…

BMC pam认证的使用

1.说明 1.1 文档参考资料 https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_ADG.htmlhttp://www.fifi.org/doc/libpam-doc/html/pam_appl-3.htmlpdf文档: https://fossies.org/linux/Linux-PAM-docs/doc/adg/Linux-PAM_ADG.pdflinux-pam 中文文档pam 旧文p…

Redis基础二(spring整合redis)

Springboot整合Redis 一、Springboot整合redis ​ redis可以通过使用java代码来实现 第一部分文档中 在终端操作redis的所有命令&#xff0c;Spring已经帮我们封装了所有的操作&#xff0c;所以变得很简单了。 ​ Spring专门提供了一个模块来进行这些操作的封装&#xff0c;这…

【Linux】详解Linux下的工具(内含yum指令和vim指令)

文章目录 前言1. Linux下软件安装的方式2. yum2.1 软件下载的小知识2.2 在自己的Linux系统下验证yum源的存在2.3 利用yum指令下载软件2.4 拓展yum源&#xff08;针对于虚拟机用户&#xff09; 3. vim编辑器3.1 vim是什么&#xff1f;3.2 如何打开vim3.2 vim各模式下的讲解3.2.1…

Oracle中ADD_MONTHS()函数详解

文章目录 前言一、ADD_MONTHS()的语法二、主要用途三、测试用例总结 前言 在Oracle数据库中&#xff0c;ADD_MONTHS()函数用于在日期中添加指定的月数。 一、ADD_MONTHS()的语法 ADD_MONTHS(date, n) 其中&#xff0c;date是一个日期值&#xff0c;n是一个整数值&#xff0c…

基于vue框架的大学生学业预警系统设计与实现53ify(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,公告信息,成绩信息,科目,学分信息,考勤信息,教师 开题报告内容 基于Vue框架的大学生学业预警系统设计与实现开题报告 一、研究背景与意义 随着高等教育的普及与深入&#xff0c;大学生群体规模日益扩大&#xff0c;其学业管理成…