JavaEE 多线程

news2025/1/18 16:58:43

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对象仍然存在,但是系统内部的线程已经执行完毕了

在这里插入图片描述

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

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

相关文章

Redis中分布式锁的使用

在分布式系统中&#xff0c;如果使用JVM中的同步锁在高并发的场景下仍然会产生线程安全问题。首先我们来查看在多个服务器时为什么会产生线程安全问题&#xff0c;有这样一个案例&#xff0c;有一件商品购买规则为一个用户只能购买一次&#xff0c;如果使用同步锁锁住用户id&am…

vue3中自定义hook函数

使用Vue3的组合API封装的可复用的功能函数 自定义hook的作用类似于vue2中的mixin技术 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂 案例: 收集用户鼠标点击的页面坐标 hooks/useMousePosition.ts文件代码&#xff1a; import { ref, onMounted, onUnmounted …

【读书笔记】微习惯

周日晚上尝试速读一本书《微习惯》&#xff0c;共七章看了下目录结构并不复杂&#xff0c;计划每章7-8分钟读完&#xff0c; 从20:15-21:00。读的时候&#xff0c;订下闹钟&#xff0c;催促着自己的进度。边读边记了一些要点和微信读书里面的划线。 第六章实践内容最为丰富&…

1949-2021年全国31省铁路里程数据

1949-2021年全国31省铁路里程数据 1、时间&#xff1a;1949-2021年 2、指标&#xff1a;时间、省份、铁路里程 3、范围&#xff1a;包括31省 4、数据缺失情况说明&#xff1a;西藏2005年之前存在缺失&#xff0c;其余30省份1978-2020年无缺失 5、来源&#xff1a;各省统计…

C++实现DFS、BFS、Kruskal算法和Prim算法、拓扑排序、Dijkstra算法

背景&#xff1a; 实现要求&#xff1a; 根据图的抽象数据类型的定义&#xff0c;请采用邻接矩阵来存储图1&#xff0c;采用邻接表来存储图2&#xff0c;并完成如下操作&#xff1a;对图1无向图进行深度优先遍历和广度优先遍历。对图1无向图采用Kruskal算法和Prim算法得出最小…

uni-app 微信小程序之自定义navigationBar顶部导航栏

文章目录 1. 实现效果2. App.vue3. pages.json 配置自定义4. 顶部导航栏 使用 微信小程序自定义 navigationBar 顶部导航栏&#xff0c;兼容适配所有机型 1. 实现效果 2. App.vue 在App.vue 中&#xff0c;设置获取的 StatusBar&#xff0c;CustomBar 高度&#xff08;实现适配…

【云原生Prometheus篇】Prometheus PromQL语句详解 1.0

文章目录 一、前言1.1 Prometheus的时间序列1.1.1 指标名称1.1.2 标签1.1.3 使用的注意事项 1.2 样本数据格式1.3 Prometheus 的聚合函数 二 、PromQL 理论部分2.1 PromQL简介2.2 PromQL的数据类型2.3 时间序列选择器2.3.1 瞬时向量选择器 &#xff08;Instant Vector Selector…

python装饰器解析(关键点:高阶函数、嵌套函数)(参数化装饰器、类装饰器)

文章目录 Python装饰器解析什么是Python装饰器基础理解 如何创建装饰器&#xff08;关键点&#xff1a;高阶函数、嵌套函数&#xff09;创建基础装饰器 使用装饰器使用示例 装饰器的返回值参数化装饰器创建参数化装饰器语法示例使用示例 类装饰器创建类装饰器语法示例使用示例 …

使用postman请求x5接口

x5接口简介 1.接口样例 {"header"{"appid":"bpmnew_fanwei","sign":"C033162E86E4CADE80C7EB44D68A5AD2","sign_type":"md5","url":"https://oa.mioffice.cn/api/bpm/xm/app/show/tod…

MySQL索引优化实战二

分页查询优化 很多时候我们业务中实现分页功能时可能会用如下SQL来实现&#xff1a; select * from employees LIMIT 10000,10表示从表中中区从10001行开始的10行记录&#xff0c;看似只查了10条记录&#xff0c;但是这条SQL是先读取10010条记录&#xff0c;然后抛弃前10000条…

【个人笔记】-python-强化学习-类-在内存中的值

{int} 数值 {int} 200 {float} 数值 {float} 0.9 {narray:维度} 数值 {narray:(1,)} [2.] {bool} True {bool} False {类名} 对象1 {类名} 对象2

LLM 开发模式 RAG,MRKL,Re-Act,Plan-Execute 模式对比

本心、输入输出、结果 文章目录 LLM 开发模式 RAG&#xff0c;MRKL&#xff0c;Re-Act&#xff0c;Plan-Execute 模式对比前言RAG、MRKL、Re-Act和Plan-Execute模式的一些对比花有重开日&#xff0c;人无再少年实践是检验真理的唯一标准 LLM 开发模式 RAG&#xff0c;MRKL&…

MySQL进阶_EXPLAIN重点字段解析

文章目录 第一节.准备1.1 版本信息1.2 准备 第二节.type2.1 system2.2 const2.3 eq_ref2.4 ref2.5 ref_or_null2.6 index_merge2.7 unique_subquery2.8 range2.9 index2.10 all 第三节. Extra3.1 No tables used3.2 No tables used3.3 Using where3.4 No matching min/max row3…

基于springboot实现的垃圾分类管理系统

一、系统架构 前端&#xff1a;html | layer | jquery | css 后端&#xff1a;springboot | mybatis 环境&#xff1a;jdk1.8 | mysql | maven 二、 代码及数据库 三、功能介绍 01. 登录页 02. 系统设置-用户管理 03. 系统设置-页面管理 04. 系统设置-角色管…

Python练习题(二)

&#x1f4d1;前言 本文主要是【Python】——Python练习题的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&am…

三极管在数字电路中的应用

一、认识三极管 三极管拥有3个引脚&#xff0c;分别对应3个级&#xff1a;基极(Base)、发射极&#xff08;Emitter&#xff09;、集电极(Collector)&#xff0c;如下图所示&#xff1b;下图横向左侧的是基极&#xff0c;带箭头的那个引脚就是发射极&#xff0c;另一个就是集电…

ElementPlus中 使用ElLoading.service, spinner: ‘el-icon-loading‘不生效

let downloadLoadingInstance ElLoading.service({ text: "正在下载数据&#xff0c;请稍候",spinner: el-icon-loading, background: "rgba(0, 0, 0, 0.7)", })使用以上代码时&#xff0c;加载的圆圈出不来&#xff0c;使用f12查看&#xff0c;即使能出…

代理模式介绍(静态代理、jdk动态代理、cglib代理)

一、静态代理 &#xff08;一&#xff09;定义 1、定义 为其他对象提供一种代理以控制对这个对象的访问&#xff1b; 2、涉及到的角色 &#xff08;1&#xff09;抽象主题角色&#xff1a;真实主题和代理主题的共同接口&#xff0c;便于在使用真实主题的地方都可以使用代理…

blue beacon rssi 指纹室内定位数据集

数据集是开展实验的基础&#xff0c;搜集并分享。如果你有关于室内定位的问题&#xff0c;请联系博主。 namedatesetpapercommentBLEBeacon: A Real-Subject Trial Dataset from Mobile Bluetooth Low Energy Beaconshttps://github.com/dimisik/BLEBeacon-Datasethttps://arxi…

React18 入门与进阶

React18 入门与进阶 前言一、核心概念与类组件使用1、虚拟DOM与新的渲染写法2、JSX 与 JSX 的使用3、类组件和函数组件4、类组件与类组件通信5、props详解与注意事项6、类组件中事件的使用7、类组件响应式数据实现与原理8、PureComponent 与 shouldComponentUpdate9、immutable…