线程与多线程编程

news2024/12/23 23:38:52

1. 线程 

1.1 概念

线程又可以称为轻量级进程 ,在进程的基础上做出了改进。

一个进程在刚刚启动时,做的第一件事就是申请内存和资源,进程需要把依赖的代码和数据,从磁盘加载到内存中这件事是比较耗费时间的,有的业务场景可能会频繁的创建,销毁进程,也就导致了大量的开销。而线程则省去了分配资源和释放资源带来的开销。

1.2 线程与进程的区别 

与进程相同,线程也可以用PCB描述,所拥有的属性也是大致相同的。不同点在于,每个线程都有自己独立的系统资源,而多个线程的系统资源是可以相互共享的。所以这些线程之间需要的重复的系统同资源就只需要申请一次避免了重复的开销

 举个例子:1线程需要资源A,B  2线程需要资源B,C  3线程需要资源 A,C。现在1线程执行了,2线程执行时就只需要申请资源 C, 再到3线程执行时则不需要再申请资源了。

也不是所有线程都可以共享资源,系统会把能资源共享的线程分成组,就称为线程组,而线程组也是进程的一部分,也就是一个进程是由多个线程组成的 

 注意:

  1. 进程是包含线程的
  2. 每个线程也是一个独立的执行流,可以执行一些代码并单独参与到cpu的调度中(状态,上下文,优先级,记账信息,每个线程都有自己的一份
  3. 每个进程有自己的资源,进程中的线程共用这一份资源(内存空间和文件描述符表)
  4. 进程是资源分配的基本单位,线程是调度执行的基本单位
  5. 同一个进程中的线程之间,可能会互相干扰引起线程安全问题
  6. 进程和进程之间不会互相影响,如果同一个进程中的线程,抛出异常,可能会影响到其他线程,把整个进程中的所有线程都终止

 2. 多线程编程

写代码的时候,可以使用多进程进行并发编程,也可以使用多线程并发编程。

在Java中是不太推荐多进程编程的,

2.1 创建线程的方法

2.1.1 继承 Thread  

1.  创建Thread子类 重写run方法

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("调用了MyThread类中的run方法");
    }
}

 run方法是该线程的入口,不需要手动调用,jvm会在线程创建好时自动调用

2. 创建子类的实例

Thread t = new MyThread();

3. 调用 Thread类中的start方法

 t.start();

注意:

  • 调用Thread类中的start方法,才会真正调用系统api在内核中创建出线程,直接调用run方法是不会创建出线程的
  • 一个Thread对象只能调用一次start方法

上面我们说了,每个线程都是一个独立的执行流,就相当于,我们创建的线程中的run方法和main方法是同时执行的

例如以下代码:

class MyThread2 extends Thread {
    @Override
    public void run() {
        while(true) {
            System.out.println("MyThread中的run方法");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread2();
        t.start();
        while(true) {
            System.out.println("main方法");
            Thread.sleep(1000);
        }
    }
}

输出结果:

如果把

t.start();

改为

t.run();

 则不会打印 “main方法” 因为 t并没有创建出线程 ,run方法是在main方法的线程中执行

2.1.2 实现 Runnable接口 

实现Runnable 接口重写 run方法

class MyThread3 implements Runnable {
    @Override
    public void run() {
        System.out.println("MyThread3中的run方法");
    }
}

 创建一个 MyThread3 对象作为参数传入 Thread的构造方法然后调用start

  public static void main(String[] args) {
        Thread t = new Thread(new MyThread3());
        t.start();
    }
2.1.3 使用匿名内部类
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("MyThread4中的run方法");
            }
        };
        t.start();
    }
}
public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("MyThread5中的run方法");
            }
        });
        t.start();
    }
}
2.1.4 使用lambda表达式
public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(()-> {
            System.out.println("MyThread6中的run方法");
        } );
        t.start();
    }
}

 推荐使用这种写法。

2.2 Thread类的属性和方法

2.2.1 构造方法
  1. Thread() 无参构造方法,创建一个新的线程对象。
  2. Thread(Runnable target)  使用指定的Runnable对象作为线程的目标,创建一个新的线程对象。
  3. Thread(String name) 使用指定的名称创建一个新的线程对象。
  4. Thread(Runnable target, String name) 使用指定的Runnable对象和名称创建一个新的线程对象。
  5. Thread(ThreadGroup group, Runnable target) 使用指定的线程组和Runnable对象创建一个新的线程对象。
  6. Thread(ThreadGroup group, Runnable target, String name) 使用指定的线程组、Runnable对象和名称创建一个新的线程对象。

 解释:每个线程都有一个名称,如果没有给它命名则会默认为Thread-0,Thread-1......累加。当一个线程运行时我们可以通过jdk的工具 jconsole 查看线程的状态,名称等。这个工具在jdk目录底下的bin文件夹中

示例代码:

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

运行该代码然后打开jconsole

如图所示:

我们在本地进程即可看到我们运行的ThreadDemo7,点击连接即可查看线程的状态等信息

线程的名字是可以重复的

2.2.2 常见的属性

解释: 

  1. id:每个线程都有一个唯一的id,用于标识线程。

  2. 名称:线程可以设置一个可选的名称来标识自己。可以通过setName(String name)方法设置线程的名称。

  3. 状态:线程在运行过程中会处于不同的状态。(下面会详细讲解)

  4. 优先级:每个线程都有一个优先级,用于指示线程在竞争CPU资源时的相对重要性。优先级范围从1到10,默认为5。可以通过setPriority(int priority)方法设置线程的优先级。

  5. 是否后台进程:线程可以设置为后台进程。后台进程不会阻止程序的终止,当所有前台线程结束时,后台线程也会自动结束。可以通过setDaemon(boolean on)方法将线程设置为后台进程

  6. 是否存活:线程是否仍然存活(即尚未终止)。

  7. 是否被中断:线程可以通过调用interrupt()方法中断自己或其他线程 以及通过interrupted()静态方法检查当前线程是否被中断,并清除中断状态。

  线程中断注意事项:

示例代码:

public class ThreadDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            //currentThread() 方法是获取当前线程的实例,即这里的t
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("ThreadDemo8");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("执行完毕");
        });
        t.start();
        Thread.sleep(5000);
        System.out.println("让线程结束");
        t.interrupt();
    }
}

当我们运行代码结果如下:

我们可以看到,代码并不像我们预期的那样结束,而是在继续运行 ,这是因为 sleep的原因,因为,当我们运行interrupt()时,sleep()可能还未结束,于是sleep就被提前唤醒了

sleep被提前唤醒会做两件事:

1. 抛出InterruptedException异常

2.将Thread对象的isInterrupted标志位设置为false

所以运行完interrupt()后标志位已经被设为true但是sleep又把它改回false了,所以会继续执行。我们要结局这个问题只需在catch中加一个break

2.2.3 join方法

如果我们希望某个线程在另一个线程之前执行完,可以用到join方法

示例代码:

public class ThreadDemo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for(int i = 0; i < 5; i++) {
                System.out.println("ThreadDemo10");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        t.join();//让main线程等待t线程执行完
        System.out.println("希望t线程执行完再执行这条语句");
    }
}

 运行结果:

执行join后,如果t线程在运行中,main线程就会阻塞(主动放弃去cpu上执行 )直到t运行结束

除了无参数的join方法还有带参数的join方法

 解释:

  • join():死等,一定会等到调用该方法的线程执行完
  • join(long millis):带超时时间的等待,如果在设定的时间内,调用该方法的线程没有执行完,则不会继续等待
  • join(long millis, int nanos):带超时时间的等待,精确到纳秒,如果在设定的时间内,调用该方法的线程没有执行完,则不会继续等待

示例代码: 

public class ThreadDemo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for(int i = 0; i < 5; i++) {
                System.out.println("ThreadDemo11");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        //让join只等2秒,如果没等到就不等了
        t.join(2000);
        System.out.println("main");
    }
}

运行结果:

 interrupt() 方法可以把阻塞 等待的join提前唤醒

 

 2.3 线程的状态

Java中有以下线程状态:

  1. NEW:线程被创建但还未开始执行。
  2. RUNNABLE:线程正在执行或准备开始执行。(就绪状态)
  3. BLOCKED:锁竞争引起的阻塞。(线程安全会详讲)
  4. WAITING:线程正在等待另一个线程的特定操作完成。(不带时间的死等,join()或wait()会进入这个状态
  5. TIMED_WAITING:线程正在等待另一个线程的特定操作完成,但设置了最大等待时间。(使用sleep()方法或者带超时时间的join()方法会进入这个状态)
  6. TERMINATED:线程已经执行完毕结束。

示例代码:

public class ThreadDemo12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for(int i = 0; i < 5; i++) {
                System.out.println("ThreadDemo12");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        //线程被创建但还没开始执行
        System.out.println(t.getState());

        t.start();
        //线程在执行中
        System.out.println(t.getState());

        t.join();
        //线程执行完毕
        System.out.println(t.getState());
    }
}

 执行结果:

 

我们也可以通过 jdk的jconsole工具查看线程的状态 

 

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

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

相关文章

国产AI边缘计算盒子,双核心A55丨2.5Tops算力

边缘计算盒子 双核心A55丨2.5Tops算力 ● 2.5TopsINT8算力&#xff0c;支持INT8/INT4/FP16多精度混合量化。 ● 4路以上1080p30fps视频编解码&#xff0c;IVE模块独立提供图像基础算子加速。 ● 支持Caffe、ONNX/PyTorch深度学习框架&#xff0c;提供resnet50、yolov5等AI算…

vue项目报错及解决npm run build:prod打包错误

vue项目报错及解决npm run build:prod打包错误 执行dev环境时加载失败了该变量&#xff0c;在package.json文件中 删掉 解决方法&#xff1a; 打包成功&#xff1a;

kerberos详解

一、介绍 kerberos概述 Kerberos始于20世纪80年代早期麻省理工学院&#xff08;MIT&#xff09;的一个研究项目&#xff0c;是一个网络身份验证系统。Kerberos提供的完整定义是安全的、单点登录的、可信的第三方相互身份验证服务。 认证过程 相关概念 KDC&#xff08;key D…

GD32 定时器输入捕获模式测量PWM占空比和频率

简介 利用GD32 定时器的PWM输入捕获模式来实现PWM波形的占空比和频率的测量。相应的简介可以参考GD32用户手册中关于定时器输入捕获的章节&#xff0c;PWM输入捕获模式是输入捕获模式的一个特例。(记录自己学习过程&#xff0c;如有错误请留言指出) 原理 如何利用定时器测量…

JavaSE-习题-循环结构等

第 1 题&#xff08;编程题&#xff09; 题目名称&#xff1a; 打印 X 图形 题目内容&#xff1a; https://www.nowcoder.com/practice/83d6afe3018e44539c51265165806ee4 假设i代表行&#xff0c;j代表列&#xff0c;当ij 或者 ij1 n&#xff0c;此时为星号。其余的都是空格。…

杨志丰:OceanBase助力企业应对数据库转型深水区挑战

11 月 16 日&#xff0c;OceanBase 在北京顺利举办 2023 年度发布会&#xff0c;正式宣布&#xff1a;将持续践行“一体化”产品战略&#xff0c;为关键业务负载打造一体化数据库。OceanBase 产品总经理杨志丰发表了《助力企业应对数据库转型深水区挑战》主题演讲。 以下为演讲…

SmartSoftHelp8,SQL语句美化,格式化,压缩

SQL语句美化&#xff0c;格式化&#xff0c;压缩 下载地址&#xff1a;https://pan.baidu.com/s/1zBgeYsqWnSlNgiKPR2lUYg?pwd8888

在oracle中的scn技术

SCN可以说是Oracle中一个很基础的部分&#xff0c;但同时它也是一个很重要的。它是系统中维持数据的一致性和顺序恢复的重要标志&#xff0c;是数据库非常重要的一种数据结构。 转载&#xff1a;深入剖析 - Oracle SCN机制详细解读 - 知乎 (zhihu.com)https://zhuanlan.zhihu.…

【计算机概论 ①】- 电脑:辅助人脑的好工具

目录 一、电脑硬件的五大单元 二、一切设计的起点&#xff1a;CPU 的架构 三、其他单元的设备 四、运行流程 五、电脑的分类 六、电脑上面常用的计算单位&#xff08;容量、速度等&#xff09; 操作系统跟硬件有相当程度的关联性&#xff0c;所以&#xff0c;如果不了解一…

详解原生Spring框架下的类切入点表达式与切入点函数

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

汉威科技家电传感器解决方案,助力智能家电市场蓬勃发展

2017年以来&#xff0c;我国家电市场承压前行&#xff0c;零售总额基本保持在9000亿元左右&#xff0c;虽然距离万亿市场只有一步之遥&#xff0c;却一直未能企及。随着物联网、传感器、AI、云计算、大数据、5G等技术的快速发展迭代&#xff0c;智能家电成为行业转型发展的突破…

C语言结构体详解(一)(能看懂文字就能明白系列)

&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;个人主页&#xff1a; 古德猫宁- &#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;…

re:Invent大会,亚马逊云科技为用户提供端到端的AI服务

11月末&#xff0c;若是你降落在拉斯维加斯麦卡伦国际机场&#xff0c;或许会在大厅里看到一排排AI企业和云厂商相关的夸张标语。走向出口的路上&#xff0c;你的身边会不断穿梭过穿着印有“AI21Lab”“Anthropic”等字样的AI企业员工。或许&#xff0c;你还会被机场工作人员主…

6大关键词:尝新/随心/低忠诚···,全面解读食品饮料行业发展趋势与消费者洞察|徐礼昭

内容&#xff1a;重构零售实验室&商派 《2023食品饮料行业零售数字化洞察报告》节选 作者&#xff1a;徐礼昭&#xff08;商派市场负责人&#xff0c;重构零售实验室负责人&#xff09; 如今品牌的影响力不再止于资本与业绩数字&#xff0c;更多是在产品核心价值以及消费…

Django HMAC 请求签名校验与 Vue.js 实现安全通信

概要 在 Web 应用的开发过程中&#xff0c;确保数据传输的安全性和完整性是一个不容忽视的问题。使用 HMAC&#xff08;Hash-based Message Authentication Code&#xff09;算法对请求内容进行签名校验&#xff0c;是一种常见且有效的安全策略。本文将详细介绍如何在 Django …

Git 简介及异常场景处理

一、简介 介绍Git之前&#xff0c;还得先介绍下 版本控制系统&#xff08;VCS&#xff09;&#xff0c; 和它的发展历史 纵观版本控制系统的发展历史&#xff0c;广义上讲&#xff0c;版本控制工具的历史可以分为三代&#xff1a; 第一代 第一代版本控制系统被称为本地版本控…

组合(回溯+剪枝、图解)

77. 组合 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 样例输入 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],…

LORA概述: 大语言模型的低阶适应

LORA概述: 大语言模型的低阶适应 LORA: 大语言模型的低阶适应前言摘要论文十问实验RoBERTaDeBERTaGPT-2GPT-3 结论代码调用 LORA: 大语言模型的低阶适应 前言 LoRA的核心思想在于优化预训练语言模型的微调过程&#xff0c;通过有效地处理权重矩阵的变化&#xff08;即梯度更新…

Docker中部署并启动RabbitMQ

目的 由于最近频繁更换云服务器&#xff0c;导致环境啥的都需要重新配置&#xff0c;关于RabbitMQ&#xff0c;我在看其他博主的文章时&#xff0c;总是不能第一时间找到想要的配置方法&#xff08;也不是没有&#xff0c;只是花的时间太久&#xff09;&#xff0c;于是打算自己…

接口响应时长几十秒问题排查以及解决

目录 背景 解决方案 总结 背景 线上系统运行几年后&#xff0c;被项目上提bug&#xff0c;有些接口响应很慢&#xff0c;加载页面要几十秒 解决方案 1、步骤一&#xff0c;加索引 性能优化成本高&#xff0c;需要开发周期&#xff0c;临时方案先分析慢sql&#xff0c;通过增…