【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.mfbz.cn/a/888417.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Web3 游戏周报(9.22 - 9.28)

回顾上周的区块链游戏概况&#xff0c;查看 Footprint Analytics 与 ABGA 最新发布的数据报告。 【9.22-9.28】Web3 游戏行业动态&#xff1a; Axie Infinity 将 Fortune Slips 的冷却时间缩短至 24 小时&#xff0c;从而提高玩家的收入。 Web3 游戏开发商 Darkbright Studios…

使用sponge+dtm快速搭建一个高性能的电商系统,秒杀抢购和订单架构的设计与实现

本文将展示如何使用 Sponge 框架快速创建一个简易版高性能电商系统&#xff0c;主要实现秒杀抢购和订单功能&#xff0c;并通过分布式事务管理器 DTM 来确保数据一致性。电商系统的架构图如下&#xff1a; 这是源码示例eshop&#xff0c;目录下包括了两个一样的代码示例&#x…

kafka-windows集群部署

kafka-windows集群部署目录 文章目录 kafka-windows集群部署目录前言一、复制出来四个kafka文件夹二、修改集群每个kafka的配置文件四、启动zookeeper&#xff0c;kafka集群 前言 部署本文步骤可以先阅读这一篇博客&#xff0c;这篇是关于单机kafka部署测试的。本文用到的文件…

Android 电源管理各个版本的变动和限制

由于Android设备的电池容量有限&#xff0c;而用户在使用过程中会进行各种高耗电操作&#xff0c;如网络连接、屏幕亮度调节、后台程序运行等&#xff0c;因此需要通过各种省电措施来优化电池使用‌&#xff0c;延长电池续航时间&#xff0c;提高用户体验&#xff0c;并减少因电…

基于LORA的一主多从监测系统_AHT20温湿度传感器

1&#xff09;AHT20温湿度传感器 这个传感器&#xff0c;网上能找到的资料还是比较多的&#xff0c;我们使用的是HAL硬件i2c&#xff0c;相比于模拟i2c&#xff0c;我们不需要过于关注时序问题&#xff0c;我们只需要关心如何获取数据以及数据如何处理&#xff0c;下面以数据手…

Prometheus之Pushgateway使用

Pushgateway属于整个架构图的这一部分 The Pushgateway is an intermediary service which allows you to push metrics from jobs which cannot be scraped. The Prometheus Pushgateway exists to allow ephemeral and batch jobs to expose their metrics to Prometheus. S…

MATLAB APPdesigner中的日期选择器怎样实时显示时间

文章目录 1.问题描述2.代码设置代码示例解释 1.问题描述 我们在做MATLAB的时候&#xff0c;一般需要在APP界面中加上时间显示&#xff0c;像下图中的右上角&#xff0c;在组件中有日期选择器&#xff0c;但是这个并不是实时显示的&#xff0c;我们还需要自己进行设置。 2.代码…

(11)MATLAB莱斯(Rician)衰落信道仿真2

文章目录 前言一、莱斯衰落信道仿真模型二、仿真代码与结果1.仿真代码2.仿真结果画图 三、后续&#xff1a;四、参考文献&#xff1a; 前言 首先给出莱斯衰落信道仿真模型&#xff0c;该模型由直射路径分量和反射路径分量组成&#xff0c;其中反射路径分量由瑞利衰落信道模型构…

力扣之603.连续空余座位

文章目录 1. 603.连续空余座位1.1 题干1.2 准备数据1.3 思路分析1.4 解法1.5 结果截图 1. 603.连续空余座位 1.1 题干 表: Cinema ----------------- | Column Name | Type | ----------------- | seat_id | int | | free | bool | ----------------- Seat_id 是该表的自动递…

宏队列和微队列

1、javascript是一个单线程语言。 javascript 语言的目的&#xff08;待补充&#xff09; 2、主线程执行完成之后&#xff0c;再执行微队列&#xff0c;微队列执行完成后再执行宏队列 3、promise的构造函数传入的回调函数是同步执行 4、promise的then函数会直接加入微队列&…

zookeeper选举kafka集群的controller

zookeeper选举kafka集群的controller目录 文章目录 zookeeper选举kafka集群的controller目录前言一、实操体验controller的选举二、模拟controller选举四、删除controller节点 前言 kafka集群的controller是kafka集群中一个有特殊作用的broker&#xff0c;负责整个kafka集群的…

用java编写飞机大战

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

Nginx06-静态资源部署

零、文章目录 Nginx06-静态资源部署 1、静态资源概述 静态资源&#xff1a;是在Web开发中不经常改变的文件&#xff0c;比如图片、CSS样式表、JavaScript脚本文件等。这些资源通常是预先编译好的&#xff0c;不需要服务器端的动态处理。动态资源&#xff1a;是在Web开发中需…

前端编程艺术(3)---JavaScript

目录 1.JavaScript 1.输出 2.变量和数据类型 3.运算符 4.数组 5.函数 6.面向对象 7.ES6面向对象 2.BOM 1.document对象 3.DOM 4.JSON 1.JavaScript JavaScript是一种脚本编程语言&#xff0c;通常用于为网页增加交互性和动态效果。它是一种高级语言&#xff…

面试(十)

目录 一. 单元测试 二. FreeRTOS和裸机哪个实时性好&#xff1f; 三. 怎么判断某个程序的运行时间 四. 函数指针 五. 全局变量被线程使用冲突 5.1 使用互斥锁 5.2 使用读写锁 5.3 使用原子操作 六. 局部变量没有初始化是什么值 七. uint_8 n 255 , n等于多少 八. …

【springboot】简易模块化开发项目整合Swagger2

接上一项目【springboot】简易模块化开发项目整合MyBatis-plus&#xff0c;进行拓展项目 1.新建模块 右键项目→New→Module&#xff0c;新建一个模块 父项目选择fast-demo&#xff0c;命名为fast-demo-config&#xff0c;用于存放所有配置项 添加后&#xff0c;项目结构如图…

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

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

Flink 03 | 数据流基本操作

Flink数据流结构 DataStream 转换 通常我们需要分析的业务数据可能存在如下问题&#xff1a; 数据中包含一些我们不需要的数据 数据格式不方面分析 因此我们需要对原始数据流进行加工&#xff0c;比如过滤、转换等操作才可以进行数据分析。 “ Flink DataStream 转换主要作…

PHP变量(第④篇)

本栏目教学是php零基础到精通&#xff0c;如果你还没有安装php开发工具请查看下方链接&#xff1a; Vscode、小皮面板安装-CSDN博客 今天来讲一讲php中的变量&#xff0c;变量是用于存储信息的"容器"&#xff0c;这些数据可以在程序执行期间被修改&#xff08;即其…

nginx配置https加密

安装nginx 官网&#xff1a; https://nginx.org/ yum安装&#xff1a; https://nginx.org/en/linux_packages.html /etc/yum.repos.d/nginx.repo [nginx-stable] namenginx stable repo baseurlhttp://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck1 enabled1 …