JavaEE 多线程

JavaEE 多线程

文章目录

  • JavaEE 多线程
    • 引子
    • 多线程
      • 1. 特性
      • 2. Thread类
        • 2.1 概念
        • 2.2 Thread的常见构造方法
        • 2.3 Thread的几个常见属性
        • 2.4 启动一个线程
        • 2.5 中断一个线程
        • 2.6 等待一个线程
        • 2.7 获取当前线程引用
        • 2.8 休眠当前线程
      • 3. 线程状态

引子

当进入多线程这一块内容时,我们之前对代码的逻辑认知可能会被颠覆!

为什么这么说?让我们看看下面这段代码:

package demo1;

public class Test1 {
    public static void main(String[] args) {

        boolean judge = true;
        while (judge) {
            System.out.println("你出不去了!!");
        }
        judge = false;
        System.out.println("我出来了!!");

    }
}

从单线程的角度,这是一个逻辑闭环,死循环后面的代码无法被执行,它"永远都出不去"!代码会一直陷入循环之中:在这里插入图片描述

但对于多线程来说,死循环也阻止不了它:

在这里插入图片描述

为什么能够做到这一点?这就涉及到多线程的特性了!

多线程

1. 特性

  • 每个线程都是一个独立的执行流
  • 多个线程之间是“并发”执行的

线程的调用方法我们使用lambda表达式(之前的文章有对其进行讲解),看看下面的代码:

package demo1;

import static java.lang.Thread.sleep;

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("你好!");
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            
        });
        t.start(); // 在这里才开始创建线程
        while (true) {
            System.out.println("你也好!!");
            sleep(1000);
        }
    }
}

在这里插入图片描述

从上述执行结果可以看出,两个线程都在并发执行,大大提高了程序整体的运行效率!!

注:

  1. main函数本身为主线程

  2. 仔细观察可以发现,两个线程执行的先后顺序是不确定的,这和线程的"随机调度"有关:

    • 一个线程,什么时候被调度到cpu执行是不确定的
    • 一个线程,什么时候从cpu上下来,给其它线程让位是不确定的

    这种“随机调度”的特性很可能会造成"抢占式执行",从而造成线程安全问题

  3. 只有调用start()方法后t线程才会被创建

  4. **sleep()**方法可以对线程进行休眠,参数以毫秒为单位

2. Thread类

2.1 概念

Thread类是JVM用来管理线程的一个类,且每个线程都有一个唯一的Thread对象与之关联

每个执行流需要有一个对象来描述,而Thread类的对象就是用来描述一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。

2.2 Thread的常见构造方法
方法说明
Thread()创建线程对象
Thread(Runnable target)使用Runnable创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用Runnable创建线程对象,并命名
Runnable runnable = new Runnable() {
    @Override
    public void run() {
    }
};
Thread t1 = new Thread();
Thread t2 = new Thread(runnable);
Thread t3 = new Thread("线程3");
Thread t4 = new Thread(runnable, "线程4");
2.3 Thread的几个常见属性
属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台进程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
  • ID是线程的唯一标识,不同线程不会重复

  • 优先级高的线程理论上来说更容易被调度到

  • 是否存活,可以理解为run方法是否运行结束了

  • JVM会在一个进程的所有非后台线程结束后,才会结束运行

    // 默认线程t为前台线程,前台线程未结束时,整个进程不会结束
    package demo2;
    
    public class Test5 {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() -> {
               for (int i = 0;i < 10;i++) {
    
                   try {
                       System.out.println(Thread.currentThread().getName() +  ":本线程还活着");
                       Thread.sleep(1000);
    
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
               System.out.println(Thread.currentThread().getName() + ":本线程将消失");
            });
    
    
            System.out.println("ID-" + t.getId());
            System.out.println("名称-" + t.getName());
            System.out.println("状态-" + t.getState());
            System.out.println("优先级-" + t.getPriority());
            System.out.println("后台线程-" + t.isDaemon());
    
            t.start();
    
            for (int j = 0;j < 5;j++) {
                System.out.println("我是主线程");
                Thread.sleep(1000);
    
            }
            System.out.println("主线程结束了");
        }
    
    }
    

    在这里插入图片描述

    // 这里修改t为后台线程,则主线程结束整个进程就结束
    package demo2;
    
    public class Test5 {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() -> {
               for (int i = 0;i < 10;i++) {
    
                   try {
                       System.out.println(Thread.currentThread().getName() +  ":本线程还活着");
                       Thread.sleep(1000);
    
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
               System.out.println(Thread.currentThread().getName() + ":本线程将消失");
            });
    
            t.setDaemon(true); // 这里修改t为后台线程,则主线程结束整个进程就结束
    
            System.out.println("ID-" + t.getId());
            System.out.println("名称-" + t.getName());
            System.out.println("状态-" + t.getState());
            System.out.println("优先级-" + t.getPriority());
            System.out.println("后台线程-" + t.isDaemon());
    
            t.start();
    
            for (int j = 0;j < 5;j++) {
                Thread.sleep(2000);
                System.out.println("我是主线程");
    
            }
            System.out.println("主线程结束了");
        }
    }
    

    在这里插入图片描述

2.4 启动一个线程

之前我们通过覆写run方法创建了一个线程对象,但线程对象被创建出来并不代表着线程就开始运行了,我们需要通过调用start()方法才真正在操作系统的底层创建出一个线程:

public class Test6 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("hh");
        });
        // 此时线程并未完全创建
    }
}

在这里插入图片描述

调用t.start()方法后,才算创建线程成功,同时自动调用run方法(被覆写):

在这里插入图片描述

2.5 中断一个线程

目前常见的中断线程方式有以下两种:

  1. 用共享的标记来进行沟通

    package demo2;
    
    public class Test7 {
        public static volatile boolean isQuit = false; // 这里需要给标志位加上volatile关键字
        public static void main(String[] args) throws InterruptedException {
    
            Thread t = new Thread(() -> {
                while (!isQuit) {
                    System.out.println("我是一个线程,正在工作中");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
    
            t.start();
            for (int i = 5;i >= 0;i--) {
                System.out.println("倒计时: " + i);
                Thread.sleep(1000);
            }
            System.out.println("让线程结束工作");
            isQuit = true;
            System.out.println("线程工作结束!");
    
        }
    }
    

    在这里插入图片描述

  2. 使用thread对象的interrupted()方法来通知线程结束

    方法说明
    Thread.currentThread().isInterrupted()判断当前线程中断标志是否设置
    Thread.currentThread().Interrupt()设置中断标志中断该线程

    注:Thread.currentThread()操作是获取当前的线程实例(t),哪个线程调用,得到的就是哪个线程的实例

    package demo2;
    
    public class Test8 {
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread t = new Thread(() -> {
                while (!Thread.currentThread().isInterrupted()) {
    
                    try {
                        System.out.println("我是一个线程,正在工作中");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
    
            t.start();
            for (int i = 5;i >= 0;i--) {
                System.out.println("倒计时: " + i);
                Thread.sleep(1000);
            }
            System.out.println("让线程结束工作");
            t.interrupt();
            System.out.println("线程工作结束!");
    
        }
    }
    

    在这里插入图片描述

注:thread收到通知的方式有两种:

  1. 如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以InterruptedException异常的形式通知,同时清除中断标准

    当出现InterruptedException的时候,要不要结束线程取决于catch中代码的写法,可以选择忽略这个异常,也可以通过break跳出循环结束线程;

  2. 如果只是内部的一个中断标志被设置,thread可以通过;

    Thread.currentThread().isInterrupted()判断指定线程的中断标志是否设置,不清除中断标志,这种方式通知收到的更及时,即使线程正在sleep也可以马上收到。

2.6 等待一个线程

有时候一个线程需要等待另一个一个线程完成它的工作后,才能进行自己的下一步工作。这个时候可以通过join()方法来进行线程等待。

join(): 在哪个线程中调用join方法则当前线程要等待引用线程执行完后才能执行,如在主线程中调用t.join();则主线程要等待t线程执行完后才能执行

package demo2;

public class Test9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0;i < 5;i++) {
                System.out.println("t-线程工作中!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();
        t.join();
        System.out.println("main 执行了");

    }
}

在这里插入图片描述

join()方法也可以设置时间限制,最多等待X毫秒,时间一到线程就停止阻塞等待:

t.join(2000);

在这里插入图片描述

2.7 获取当前线程引用

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

package demo2;

public class Test10 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
    }
}

在这里插入图片描述

2.8 休眠当前线程

public static void sleep (long millis) throws InterruptedException; 休眠当前进程millis毫秒

注:因为线程的调度是不可控的,所有这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的

package demo2;

public class Test11 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("开始时间:" + System.currentTimeMillis());
        Thread.sleep(3000); // 休眠主线程
        System.out.println("结束时间:" + System.currentTimeMillis());
    }
}

在这里插入图片描述

3. 线程状态

  • NEW: Thread对象创建好了,但是还没有调用start方法在系统中创建线程;
  • RUNNABLE:就绪状态,表示这个线程正在cpu上执行或准备就绪随时可以去cpu上执行;
  • TIME_WATING: 表示指定时间的阻塞,达到一定时间后会自动解除阻塞,一般是调用sleep方法或有时间参数的join方法时会进入该状态;
  • WATINGT: 表示不带时间的阻塞(死等),必须要满足一定条件才会解除阻塞,一般调用wait方法join方法会进入该状态;
  • BLOCKED: 锁竞争引起的阻塞;
  • TERMINATED: Thread对象仍然存在,但是系统内部的线程已经执行完毕了

在这里插入图片描述

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

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

相关文章

《微信小程序开发从入门到实战》学习四十

4.2 云开发JSON数据库 4.2.11 更新数据 使用数据库API更新数据有两种方法&#xff1a;一.将记录局部更新的update方法&#xff1b;二.以替换的方式更新记录的set方法 update方法可以局部更新一个记录或一个集合的多个记录&#xff0c;更新时只有指定字段更新&#xff0c;其他…

基于英特尔平台及OpenVINO2023工具套件优化文生图任务

当今&#xff0c;文生图技术在很多领域都得到了广泛的应用。这种技术可以将文本直接转换为逼真的图像&#xff0c;具有很高的实用性和应用前景。然而&#xff0c;由于文生成图任务通常需要大量的计算资源和时间&#xff0c;如何在英特尔平台上高效地完成这些计算是一个重要的挑…

Spring Cloud Alibaba简介

1、简介 Spring Cloud阿里(https://sca.aliyun.com/en-us/)为分布式应用开发提供一站式解决方案。它包含开发分布式应用程序所需的所有组件&#xff0c;使您可以轻松地使用Spring Cloud开发应用程序。 有了Spring Cloud阿里&#xff0c;你只需要添加一些注释和少量的配置&#…

32、直流电机驱动(PWM)

直流电机介绍 直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极&#xff0c;当电极正接时&#xff0c;电机正转&#xff0c;当电极反接时&#xff0c;电机反转 直流电机主要由永磁体&#xff08;定子&#xff09;、线圈&#xff08;转子&#xff09;和换向器…

[传智杯 #3 决赛] 面试

题目背景 disangan233 和 disangan333 去面试了&#xff0c;面试官给了一个问题&#xff0c;热心的你能帮帮他们吗&#xff1f; 题目描述 现在有 n 个服务器&#xff0c;服务器 i 最多能处理 ai​ 大小的数据。 接下来会有 k 条指令 bk​&#xff0c;指令 i 表示发送 bi​ …

JavaWeb-XML

1.常见的配置文件 1.1 properties 数据库的连接就使用properties文件作为配置文件&#xff0c;properties文件中的配置信息是以键值对的形式存储的。 beiluo.jdbc.urljdbc:mysql://localhost:3306/beiluo beiluo.jdbc.drivercom.mysql.cj.jdbc.Driver beiluo.jdbc.usernamer…

基于Java SSM框架实现师生交流答疑作业系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现师生交流答疑作业系统演示 摘要 在新发展的时代&#xff0c;众多的软件被开发出来&#xff0c;给用户带来了很大的选择余地&#xff0c;而且人们越来越追求更个性的需求。在这种时代背景下&#xff0c;人们对师生交流平台越来越重视&#xff0c;更好的实…

6-13连接两个字符串

#include<stdio.h> int main(){int i0,j0;char s1[222],s2[333];printf("请输入第一个字符串&#xff1a;\n");gets(s1);//scanf("%s",s1);printf("请输入第二个字符串&#xff1a;\n");gets(s2);while(s1[i]!\0)i;while(s2[j]!\0)s1[i]s2…

DevEco Studio 调整开发工具中的字体大小与行高

我们打开编辑器 选择 左上角 File 下的 Settings 将左侧菜单栏 编辑 展开 我们在编辑下面 选择 Font 然后 如下图指向的两个位置 我们可以调整它的字体大小和行高 设置好之后 右下角 点击 Apply 应用 然后点击 OK即可 当然 你按着 Ctrl 然后鼠标滚动 也可以像浏览器那样 拉…

React如何像Vue一样将css和js写在同一文件

如果想在React中想要像Vue一样把css和js写到一个文件中&#xff0c;可以使用CSS-in-JS。 使用CSS-in-JS 下载 npm i styled-components使用 就像写scss一样&#xff0c;不过需要声明元素的类型 基本语法及展示如下&#xff0c; import styled from "styled-component…

03 数仓平台 Kafka

kafka概述 定义 Kafka 是一个开源的分布式事件流平台&#xff08;Event Streaming Plantform&#xff09;&#xff0c;主要用于大数据实时领域。本质上是一个分布式的基于发布/订阅模式的消息队列&#xff08;Message Queue&#xff09;。 消息队列 在大数据场景中主要采用…

Python编程技巧 – 迭代器(Iterator)

Python编程技巧 – 迭代器(Iterator) By JacksonML Iterator(迭代器)是Python语言的核心概念之一。它常常与装饰器和生成器一道被人们提及&#xff0c;也是所有Python书籍需要涉及的部分。 本文简要介绍迭代器的功能以及实际的案例&#xff0c;希望对广大读者和学生有所帮助。…

YOLOv5改进 | 添加ECA注意力机制 + 更换主干网络之ShuffleNetV2

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。本文给大家介绍一种轻量化部署改进方式&#xff0c;即在主干网络中添加ECA注意力机制和更换主干网络之ShuffleNetV2&#xff0c;希望大家学习之后&#xff0c;能够彻底理解其改进流程及方法~&#xff01;&#x1f308; 目…

使用idea如何快速的搭建ssm的开发环境

文章目录 唠嗑部分言归正传1、打开idea&#xff0c;点击新建项目2、填写信息3、找到pom.xml先添加springboot父依赖4、添加其他依赖5、编写启动类、配置文件6、连接创建数据库、创建案例表7、安装MybatisX插件8、逆向工程9、编写controller10、启动项目、测试 结语 唠嗑部分 小…

剪切空间与归一化设备坐标【NDC】

有了投影变换的知识&#xff0c;我们现在可以讨论剪切空间&#xff08;Clip Space&#xff09;和 归一化设备坐标&#xff08;NDC&#xff1a;Normalized Device Coordinates&#xff09;。 为了理解这些主题&#xff0c;我们还需要深入了解齐次坐标的有趣世界。 NSDT工具推荐&…

解决:UnboundLocalError: local variable ‘js’ referenced before assignment

解决&#xff1a;UnboundLocalError: local variable ‘js’ referenced before assignment 文章目录 解决&#xff1a;UnboundLocalError: local variable js referenced before assignment背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使…

Python、Stata、SPSS怎么学?推荐一波学习资料

1.Python学习推荐书目 关于Python机器学习&#xff0c;推荐学习杨维忠、张甜所著的&#xff0c;清华大学出版社出版的《Python机器学习原理与算法实现》&#xff0c;以及张甜、杨维忠所编著的&#xff0c;清华大学出版社出版的《Python数据科学应用从入门到精通》&#xff0c;…

柯桥英语口语学习,日常生活用语军大衣用英语怎么说?

那么军大衣跟羽绒服用英语怎么说呢&#xff1f; 跟商英君一起学习一下吧&#xff01; 01 "军大衣"用英语怎么说&#xff1f; 军大衣在英语表达中 也有专门的词汇 即military coat 或 military style cotton coats military有“军人、军事;军事的、军用的…”的…

【Java Web学习笔记】3 - JavaScript入门

项目代码 https://github.com/yinhai1114/JavaWeb_LearningCode/tree/main/javascript 零、JavaScript引出 JavaScript 教程 官方文档 1. JavaScript能改变HTML内容&#xff0c;能改变HTML属性&#xff0c;能改变HTML样式(CSS),能完成页面的数据验证。 <!DOCTYPE html>…