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

news2024/10/8 23:47:26

目录

  • 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.coloradmin.cn/o/2197499.html

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

相关文章

WPS Office从路径穿越到远程代码执行漏洞(CVE-2024-7262)分析与复现

漏洞概述 WPS Office程序promecefpluginhost.exe存在不当路径验证问题&#xff0c;允许攻击者在Windows上加载任意Windows库文件。该漏洞已被APT-C-60攻击者利用&#xff0c;当用户打开MHTML格式的文档时&#xff0c;只需单击一个恶意制作的超链接&#xff0c;即可执行攻击者指…

【C++】map详解

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

如何实现小红点

文章目录 1. 概念介绍2. 实现方法3 示例代码我们在上一章回中介绍了WebView组件相关的内容,本章回中将介绍如何在图标旁边添加小红点.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 在实际项目中有时候需要在图标旁边显示小红点,而且小红点内还有数字,比如购物车图标显…

Android阶段学习思维导图

前言 记录下自己做的一个对Android原生应用层的思维导图&#xff0c;方便个人记忆扩展&#xff1b;这里只露出二级标题。 后语 虽然有些内容只是初步了解&#xff0c;但还是记录了下来&#xff1b;算是对过去一段学习的告别。

全体起立!CEEMDAN-Kmeans-VMD-CNN-Attention双重分解+卷积神经网络注意力机制多元时间序列预测

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CEEMDAN-Kmeans-VMD-CNN-Attentionr融合K均值聚类的数据双重分解卷积神经网络注意力机制多元时间序列预测&#xff08;完整源码和数据&#xff09; 2.CEEMDAN分解&#xff0c;计算样本熵&#xff0c;根据…

Arduino UNO R3自学笔记23 之 Arduino如何使用4511控制数码管?

注意:学习和写作过程中,部分资料搜集于互联网,如有侵权请联系删除。 前言:因为7段数码管控制需要用到7个IO,这会严重占用Arduino的IO口,因此我们采用现有IC来节省Arduino的IO口。 1.CD4511介绍 CD4511是一款用于驱动共阴极LED(数码管)显示器的BCD码-七段码译码器。它…

机器学习-支撑向量机SVM

Support Vector Machine 离分类样本尽可能远 Soft Margin SVM scikit-learn中的SVM 和kNN一样&#xff0c;要做数据标准化处理&#xff01; 涉及距离&#xff01; 加载数据集 import numpy as np import matplotlib.pyplot as plt from sklearn import datasetsiris datas…

CentOS7 虚拟机操作系统安装及相关配置教程

1、安装虚拟机 在VMware《主页》界面中点击《创建新的虚拟机》按钮&#xff1a; 选择你准备好的ISO文件&#xff0c;点击下一步&#xff1a; 然后填写虚拟机的名称以及虚拟机将来保存的位置&#xff1a; 再次下一步&#xff0c;填写虚拟机磁盘大小&#xff1a; 继续下一步&…

ES postman操作全量修改,局部修改,删除

全量修改 修改需要调用的url 地址是http://192.168.1.108:9200/shopping/_doc/1001&#xff0c;调用方法使用put 只修改指定的需求的内容的请求方式 post方式就是局部修改 http://192.168.1.108:9200/shopping/_update/1001&#xff0c;请求方式post 上图是只修改id 为1001数…

sqli-labs less-18 http头user-agent注入

HTTP头注入 常见的HTTP注入点产生位置为【Referer】、【X-Forwarded-For】、【Cookie】、【X-Real-IP】、【Accept-Language】、【Authorization】 HTTP Referer是header头的一部分&#xff0c;从哪个网页了链接过来的 X-Forwarded-For 简称XXF头&#xff0c;代表客户端&#…

【计算机网络】Tcp/IP五层协议,Udp报文组成,Udp与Tcp的区别

Tcp/IP五层协议 TCP/IP模型是计算机网络的核心协议之一&#xff0c;通常被分为五层&#xff0c;每一层都有其独特的功能和作用。以下是TCP/IP模型的五层协议的简要描述&#xff1a; 物理层&#xff1a;这一层涉及实际的物理连接&#xff0c;定义了硬件传输介质的特性&#xff…

网络安全现在的前景是如何的?_网络安全技术研究生可否从事大数据工作

从当前的人才培养体系来看&#xff0c;网络安全人才的培养既有本科教育和专科教育&#xff0c;同时也有研究生教育&#xff0c;所以要想成为网络安全人才&#xff0c;途径还是比较多的&#xff0c;可以根据自身的实际情况来选择不同的教育方式。对于当前的职场人来说&#xff0…

C++ STL容器(五) —— priority_queue 底层剖析

这篇来讲下 priority_queue&#xff0c;其属于 STL 的容器适配器&#xff0c;容器适配器是在已有容器的基础上修改活泼限制某些数据接口以适应更特定的需求&#xff0c;比如 stack 栈使数据满足后进先出&#xff0c;queue 队列使数据满足先进先出&#xff0c;其都是在已有容器上…

【重学 MySQL】六十一、数据完整性与约束的分类

【重学 MySQL】六十一、数据完整性与约束的分类 数据完整性什么是约束约束的分类如何查看、添加和删除约束查看约束添加约束删除约束 在MySQL中&#xff0c;数据完整性是确保数据库中数据的准确性和一致性的关键。为了实现数据完整性&#xff0c;MySQL提供了多种约束类型&#…

【Qt】窗口预览(1)—— 菜单栏

窗口预览&#xff08;1&#xff09; 1. QMainWindow2. QMenuBar——菜单栏2.1 创建菜单栏/将菜单栏添加到widget中2.2 addMenu——在菜单栏中添加菜单2.3 在菜单中添加选项2.4 添加快捷键2.5 支持嵌套添加菜单2.6 添加信号2.7 添加分割线 1. QMainWindow Qt窗口是通过QMainWin…

插件-发送邮件通知

有时候通过python运行程序&#xff0c;在出现异常时&#xff0c;需要进行邮件通知&#xff0c;可能还需要截图。比如对浏览器进行控制时出现了异常&#xff0c;则需要进行截图分析。 email-validator 2.0.0.post2 import asyncio import logging import smtpli…

C++基础面试题 | C++中野指针和悬挂指针的区别?

文章目录 回答重点&#xff1a;1. 野指针&#xff08;Wild Pointer&#xff09;&#xff1a;2. 悬挂指针&#xff08;Dangling Pointer&#xff09;&#xff1a; 拓展知识&#xff1a;如何避免这些问题野指针和悬挂指针 回答重点&#xff1a; 在C中&#xff0c;野指针是指未初…

职场上的人情世故,你知多少?这五点一定要了解

职场是一个由人组成的复杂社交网络&#xff0c;人情世故在其中起着至关重要的作用。良好的人际关系可以帮助我们更好地融入团队&#xff0c;提升工作效率&#xff0c;甚至影响职业发展。在职场中&#xff0c;我们需要了解一些关键要素&#xff0c;以更好地处理人际关系&#xf…

计算机网络:物理层 —— 信道复用技术

文章目录 信道信道复用技术信道复用技术的作用基本原理常用的信道复用技术频分复用 FDM时分复用 TDM波分复用 WDM码分复用 CDM码片向量基本原理 信道 信道是指信息传输的通道或介质。在通信中&#xff0c;信道扮演着传输信息的媒介的角色&#xff0c;将发送方发送的信号传递给…