【JavaEE初阶】应是天仙狂醉,乱把白云揉碎 - (重点)线程

在这里插入图片描述

本篇博客给大家带来的是线程的知识点, 由于内容较多分几天来写.
🐎文章专栏: JavaEE初阶
🚀若有问题 评论区见
⭐欢迎大家点赞 评论 收藏 分享 ❤❤❤
如果你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .

1. 认识线程

1.1 概念

)1 线程是什么

⼀个线程就是⼀个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 “同时” 执行着多份代码.

⼀家公司要去银⾏办理业务,既要进⾏财务转账,⼜要进⾏福利发放,还得进⾏缴社保。
如果只有张三⼀个会计就会忙不过来,耗费的时间特别⻓。为了让业务更快的办理好,张三⼜找来两位同事李四、王五⼀起来帮助他,三个⼈分别负责⼀个事情,分别申请⼀个号码进⾏排队,⾃此就有了三个执⾏流共同完成任务,但本质上他们都是为了办理⼀家公司的业务。
此时,我们就把这种情况称为多线程,将⼀个⼤任务分解成不同⼩任务,交给不同执⾏流就分别排队执⾏。其中李四、王五都是张三叫来的,所以张三⼀般被称为主线程(Main Thread)。

)2 为什么要有线程

⾸先, “并发编程” 成为 “刚需(必备)”.

• 单核 CPU 的发展遇到了瓶颈. 要想提⾼算⼒, 就需要多核 CPU. ⽽并发编程能更充分利⽤多核 CPU资源.
• 有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做⼀些其他的⼯作, 也需要⽤到并发编程.

其次, 虽然多进程也能实现并发编程, 但是线程比进程更轻量.

• 创建线程比创建进程更快.
• 销毁线程比销毁进程更快.
• 调度线程比调度进程更快.
总结就是线程创建更快,销毁更快,调度更快.

最后, 线程虽然比进程轻量, 但是⼈们还不满足, 于是又有了 “线程池”(ThreadPool) 和 (Coroutine)“协程”.(这两个后续的文章再说).

)3 进程和线程的区别(经典面试题)

• 1. 进程是包含线程的. 每个进程⾄少有⼀个线程存在,即主线程.
• 2. 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间.

比如上面的多进程例子中,每个客户来银行办理各自的业务,但他们之间的票据肯定是不想让别人知道的,否则钱不就被其他⼈取走了么。而上面我们的公司业务中,张三、李四、王五虽然是不同的执行流,但因为办理的都是⼀家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最大区别。

• 3. 进程是系统分配资源的最小单位,线程是系统调度的最小单位.
• 4. ⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带走(整个进程崩溃).
• 5. 都是用来实现并发编程场景的. 但是线程比进程更轻量, 更高效.

在这里插入图片描述

)4 Java的线程 和 操作系统线程 的关系

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了⼀些 API 供用户
使用(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进⼀步的抽象和封装.

1.2 创建线程的方式(面试题)

⽅法1 继承 Thread 类

一. 继承 Thread重写 run()方法 来创建⼀个线程类.

 class MyThread extends Thread {
    @Override
    public void run() {
        //这个方法就是线程的入口方法
        while(true) {
            System.out.println("hello thread");
            try {
            //sleep() 让线程休眠 x ms(毫秒).
            
                Thread.sleep(1000);//此处抛异常只能 try catch 不能 throws 因为父类Thread没有throws, 子类也不行
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

二.创建 MyThread 类的实例

Thread t = new MyThread(); //向上转型

三. 调用 start 方法启动线程

t.start();
//创建线程的第一种写法
//创建一个类, 继承自 Thread
    class MyThread extends Thread {
    @Override
    public void run() {
        //这个方法就是线程的入口方法
        while(true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);//此处抛异常只能 try catch 不能 throws 因为父类Thread没有throws, 子类也不行
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

//创建线程
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread(); //Java生态更鼓励向上转型
       // 封装本质上是让调用者不用了解类实现的细节降低了学习和使用的成本.
//多态则是在封装的基础上更进一步,多态则是都不需要你知道当前是啥类。
        //start 和 run 都是线程的入口(线程要做什么)
        //run只是描述了线程的入口 (线程要做什么任务)
        //start 则是真正调用了系统API, 在系统中创建出线程, 让线程再调用 run.
        t.start();
        //t.run();
        while(true) {
            System.out.println("hello main");
            Thread.sleep(1000); //自定义类 两种抛异常方式都可以.
        }
    }
}

方法2. 实现 Runnable 接⼝

一. 实现 Runnable 接口,重写run()方法.

class MyRunnable implements Runnable {
    @Override
    public void run() {
        while(true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

二. 创建 Thread 类实例, 调⽤ Thread 的构造⽅法时将 Runnable 对象作为 target 参数.

Runnable runnable = new MyRunnable();

三. 调用 start 方法

//创建线程的第二种写法
    //实现Runnable接口,重写run方法.
   //使用 Runnable 的写法,和 直接继承 Thread 之间的区别, 主要就是三个字,解耦合
class MyRunnable implements Runnable {
    @Override
    public void run() {
        while(true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo2 {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

方法3. 匿名内部类创建 Thread ⼦类对象

public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        t.start();
        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

方法4. 匿名内部类创建 Runnable ⼦类对象

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

        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

方法5. lambda 表达式创建 Runnable ⼦类对象(常用)

public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while(true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();

        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

2. Thread 类及常见方法

Thread 类是 JVM ⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。

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

在这里插入图片描述

2.1 Thread 的常见构造方法


在这里插入图片描述

1 Thread t1 = new Thread();
2 Thread t2 = new Thread(new MyRunnable());
3 Thread t3 = new Thread("线程1");
4 Thread t4 = new Thread(new MyRunnable(), "线程2")

2.2 Thread 的几个常见属性

在这里插入图片描述
1. ID 是线程的唯⼀标识,不同线程不会重复.(此处ID是Java给线程分配的,不是系统API提供的线程ID,也不是PCB中的ID.)

2. 名称是各种调试工具用到.

3. 状态表示线程当前所处的⼀个情况.

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

5. 关于后台线程(守护线程),需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运行。

线程默认情况下是前台线程.
一个Java进程中,如果前台线程没有执行结束,整个进程都不会结束. 相比之下后台线程是否结束不影响整个进程的结束.

//是否后台线程, isDaemon() true变 后台,
    //线程默认是前台线程.
public class Demo6 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while(true) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "这是新线程");

        //设置 t 为后台线程
        t.setDaemon(true);

        t.start();

    }
}

6. 是否存活,即简单的理解,为 run 方法是否运行结束了

//isAlive() 方法的用法.
public class Demo7 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("线程开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程结束");
        });

        t.start();
        System.out.println(t.isAlive());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(t.isAlive() );
    }
}

在这里插入图片描述

两线程是"并发执行"的为什么是先打印 true 而不是: "线程开始"呢?
系统的调度顺序不确定, 但是大概率是先打印true, 因为调用了start方法之后, 新的线程被创建也是有一定的开销的, 创建线程的过程中, 主线程就执行了. 当然也存在先打印 "线程开始"的情况. 比如: 主线程刚好卡了一会.

7. 线程的中断问题

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 我还活着");
                            Thread.sleep(1 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 我即将死去");
        });

        System.out.println(Thread.currentThread().getName()
                + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName()
                + ": 名称: " + thread.getName());
        System.out.println(Thread.currentThread().getName()
                + ": 状态: " + thread.getState());
        System.out.println(Thread.currentThread().getName()
                + ": 优先级: " + thread.getPriority());
        System.out.println(Thread.currentThread().getName()
                + ": 后台线程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName()
                + ": 活着: " + thread.isAlive());
        System.out.println(Thread.currentThread().getName()
                + ": 被中断: " + thread.isInterrupted());
        thread.start();
        while (thread.isAlive()) {}
        System.out.println(Thread.currentThread().getName()
                + ": 状态: " + thread.getState());
    }
}

在这里插入图片描述

2.3 启动⼀个线程 - start()

之前我们已经看到了如何通过覆写 run 方法创建⼀个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。

• 覆写 run 方法是提供给线程要做的事情的指令清单.
• 线程对象可以认为是把 李四、王五叫过来了.
• 而调用 start() 方法,就是喊⼀声:”行动起来!“,线程才真正独立去执行了.

2.3.1 start方法 和 run方法 的区别(面试题)

start方法内部会调用到系统API,来在系统内核中创建出线程.(调⽤ start ⽅法, 才真的在操作系统的底层创建出⼀个线程)
run方法就只是单纯地描述了该线程要执行什么样的内容.

2.4 中断一个线程

李四⼀旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加⼀些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停⽌呢?这就涉及到我们的停止线程的方式了。

方法一. 通过共享的标记来进⾏沟通

//线程的打断
    //第一个方案 手动创建标志位
public class Demo8 {
   private static boolean isQuit = false;
   //此处isQuit不能定义到 main方法中,
    // 因为lambda表达式有一个语法规则变量捕获,可以自动的捕获到上层作用域中的局部变量.

    //实际上变量捕获还有一个前提就是 只能捕获final或者捕获一个final的变量(不改变的量)
    // 那此处也没有final修饰isQuit, 为什么lambda能访问到isQuit?

    //原来当isQuit是成员变量的时候, lambda访问它的时候就不是用变量捕获的语法了,
    //而是"内部类访问外部类属性", 此时就没有final的属性限制了.
    public static void main(String[] args) {

        Thread t = new Thread(() -> {
            // 此处的打印可以替换成任意的逻辑来表示线程的实际工作内容
            while(!isQuit) {
                System.out.println("线程工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程工作完毕!");
        });
        t.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        isQuit = true;
        System.out.println("设置 isQuit 为 true");
    }
}

方法二. 调⽤ interrupt() ⽅法来通知

使⽤ Thread.interrupted() 或者
Thread.currentThread().isInterrupted() 代替⾃定义标志位.
Thread 内部包含了⼀个 boolean 类型的变量作为线程是否被中断的标记

在这里插入图片描述

//线程终止
    //第二个方案 使用Thread类内部现有的一个标志位
public class Demo9 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            //Thread 类内部, 有一个现成的标志位,可以用来判定当前的循环是否要结束
           while(!Thread.currentThread().isInterrupted()) {
               System.out.println("线程工作中");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   //1. 假装没听见, 循环继续正常执行
                   e.printStackTrace();
                  /* throw new RuntimeException(e);*/
                   //2. 加上一个 break, 表示让线程立即结束
                   //break;
                   //3. 做一些其他工作, 完成之后再结束
                   //其他工作的代码放到这里
                   break;
               }
           }
        });
        t.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(" 让 t 线程终止");
        t.interrupt();
    }
}

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

1. 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志。 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程.
2. 否则,只是内部的⼀个中断标志被设置,thread 可以通过
。 Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志.
这种⽅式通知收到的更及时,即使线程正在 sleep 也可以马上收到。

本篇博客到这里也就结束啦, 感谢你的观看!! ❤❤❤

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

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

相关文章

构建鸿蒙5.0应用(一)

准备工作 1、开发工具 开发工具使用华为官方推荐的IDE&#xff1a;DevEco Studio &#xff0c;为鸿蒙应用开发提供了最全面的官方支持&#xff0c;包括最新的 SDK、API 和功能。 2、编译工具 开发鸿蒙应用需要安装Nodejs环境&#xff0c;为打包编译鸿蒙应用提供支持&#x…

【Linux】匿名管道通信场景——进程池

&#x1f525; 个人主页&#xff1a;大耳朵土土垚 &#x1f525; 所属专栏&#xff1a;Linux系统编程 这里将会不定期更新有关Linux的内容&#xff0c;欢迎大家点赞&#xff0c;收藏&#xff0c;评论&#x1f973;&#x1f973;&#x1f389;&#x1f389;&#x1f389; 文章目…

FUSU: 多源多时相土地利用变化分割数据集

FUSU是首个针对细粒度城市语义理解的多时态、多源地类变化分割数据集&#xff0c;其提供高分辨率双时态图像和每月时序观测&#xff0c;支持对城市动态变化的高频率监测。FUSU-Net是统一的时序架构&#xff0c;可同时进行变化检测和分割任务。结合光学和SAR数据&#xff0c;通过…

LLM学习笔记(13)分词器 tokenizer

由于神经网络模型不能直接处理文本&#xff0c;因此我们需要先将文本转换为数字&#xff0c;这个过程被称为编码 (Encoding)&#xff0c;其包含两个步骤&#xff1a; 使用分词器 (tokenizer) 将文本按词、子词、字符切分为 tokens&#xff1b;将所有的 token 映射到对应的 tok…

Unity中让光点跟图片填充区的末尾一起移动

一、实现效果展示 想要实现的效果如下,就是要让白色光点图片跟随绿色圆形图片填充区末尾一起移动。 二、代码如下: using UnityEngine; using System.Collections; using UnityEngine.UI; using DG.Tweening;public class IconCircle : MonoBehaviour {public float ti…

给定一个整数可能为正,0,负数,统计这个数据的位数.

题目描述 给定一个整数可能为正,0,负数,统计这个数据的位数. 例如1234567输出7位; -12345678输出8位;0输出1位 代码实现 int main() { long long m; long long n; scanf("%lld",&n); mn; int count0;//位数 do { count; n/10;//舍弃个位 }while(n!0); printf(&…

LLamafactory API部署与使用异步方式 API 调用优化大模型推理效率

文章目录 背景介绍第三方大模型API 介绍LLamafactory 部署API大模型 API 调用工具类项目开源 背景介绍 第三方大模型API 目前&#xff0c;市面上有许多第三方大模型 API 服务提供商&#xff0c;通过 API 接口向用户提供多样化的服务。这些平台不仅能提供更多类别和类型的模型…

【关闭or开启电脑自带的数字键盘】

目录 一、按数字键盘左上角的按键【NumLK Scroll】 二、修改注册表中数字键盘对应的数值【InitialKeyboardIndicators】 1、步骤&#xff1a; 2、知识点&#xff1a; 一、按数字键盘左上角的按键【NumLK Scroll】 这是最简单快捷的方法。 关闭后若想开启&#xff0c;再按一…

【FAQ】使用Node.js 镜像 构建本地项目

在nodejs官方并没有提供使用node.js构建本地项目的方法&#xff0c;但是通过阅读官方文档&#xff0c;可以发现&#xff0c;官方在包管理器界面提供了如下语句 所以node.js容器是可以执行语句的 下面通过docker 的 -w 、-v 参数设置容器工作目录和目录映射&#xff08;实现本…

深度学习 | pytorch + torchvision + python 版本对应及环境安装

Hi&#xff0c;大家好&#xff0c;我是半亩花海。要让一个基于 torch 框架开发的深度学习模型正确运行起来&#xff0c;配置环境是个重要的问题&#xff0c;本文介绍了 pytorch、torchvision、torchaudio 及 python 的对应版本以及环境安装的相关流程。 目录 一、版本对应 二…

4399大数据面试题及参考答案(数据分析和数据开发)

对数据分析的理解 数据分析是一个从数据中提取有价值信息以支持决策的过程。它涵盖了数据收集、清洗、转换、建模和可视化等多个环节。 首先&#xff0c;数据收集是基础。这包括从各种数据源获取数据&#xff0c;例如数据库、文件系统、网络接口等。这些数据源可以是结构化的数…

fastdds:编译、安装并运行helloworld

fastdds安装可以参考官方文档&#xff1a; 3. Linux installation from sources — Fast DDS 3.1.0 documentation 从INSTALLATION MANUAL这一节可以看出来&#xff0c;fastdds支持的操作系统包括linux、windows、qnx、MAC OS。本文记录通过源码和cmake的方式来安装fastdds的…

Istio笔记01--快速体验Istio

Istio笔记01--快速体验Istio 介绍部署与测试部署k8s安装istio测试istio 注意事项说明 介绍 Istio是当前最热门的服务网格产品&#xff0c;已经被广泛应用于各个云厂商和IT互联网公司。企业可以基于Istio轻松构建服务网格&#xff0c;在接入过程中应用代码无需更改&#xff0c;…

ipad项目 蓝湖宽度

ipad项目 横屏状态时 蓝湖宽度设置930px media screen and (orientation: portrait) {/* 竖屏时的样式 */ } media screen and (orientation: landscape) {/* 默认是 横屏时的样式 */ }

14、保存与加载PyTorch训练的模型和超参数

文章目录 1. state_dict2. 模型保存3. check_point4. 详细保存5. Docker6. 机器学习常用库 1. state_dict nn.Module 类是所有神经网络构建的基类&#xff0c;即自己构建一个深度神经网络也是需要继承自nn.Module类才行&#xff0c;并且nn.Module中的state_dict包含神经网络中…

在鸿蒙应用中 Debug 对开发者的帮助

文章目录 摘要引言Debug 的意义与挑战案例&#xff1a;页面渲染性能优化中的 Bug 排查Debug 过程详解问题定位问题解决优化布局与渲染逻辑 代码详细讲解示例代码详细讲解1. 导入必要模块2. 数据生成3. 使用虚拟列表组件items 属性itemHeight 属性renderItem 属性 4. 返回完整组…

基于多VSG独立微网的多目标二次控制MATLAB仿真模型

“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 模型简介 本文将一致性算法引入微电网的二次频率和电压控制&#xff0c;自适应调节功率参考值和补偿电压&#xff0c;同时实现频率电压恢复、有功 无功功率的比例均分以及功率振荡抑制&#xff0c;提高系统的暂态和稳…

洛谷 P2415 集合求和 C语言

题目&#xff1a; https://www.luogu.com.cn/problem/P2415 思路从大佬学来的思路。 如图&#xff1a; 我们可以发现&#xff0c;集合最后出现过的数字是2的&#xff08;n-1&#xff09;次方&#xff0c;所以就很好计算了。 代码如下&#xff1a; #include <iostream&g…

leaflet 的基础使用

目录 一、创建dom节点 二、创建地图 三、添加底图&#xff08;天地图&#xff09;&#xff0c;在地图创建完成后添加底图 本章主要讲述leaflet在vue中的使用&#xff1a; leaflet 详情总目录&#xff1a;传送 一、创建dom节点 <div class"map" id"map_…

Springboot 2.x升级到3.x

运维在扫描项目的时候发现了官方发布的漏洞&#xff0c;https://spring.io/security/cve-2024-38816 我们使用的是spring框架的2.x系列&#xff0c;WebMvc依赖于5.3系列&#xff0c;描述说需要更新到5.3.40&#xff0c;但是官方迟迟不再更新。同时发现官方说5.3系列也就更新到…