多线程之基础

news2025/2/13 18:10:24

写在前面

本文看下多线程基础相关内容。

在这里插入图片描述

1:线程基础分析

1.1:摩尔定律失效

戈登.摩尔,英特尔公司的创始人之一,其发现了一个计算机的发展规律,即,处理器的性能每24个月就会翻一倍,这就是摩尔定律,开始这个预测是正确的,但是后来处理器性能的提升速度严重放缓,这就是摩尔定律失效,既然单个CPU已经无法满足业务需求,那么就多个CPU,也就是多核,有了多核,自然就有了多线程。有了多核,多线程之后,就可以进行并行计算,提高更高的计算能力。

目前多核CPU有两种架构,共享内存架构,即多个CPU共享同一块内存,如下图:

在这里插入图片描述

多个CPU通过消息总线BUS,访问同一块内存,这种架构方式存在如下问题:

1:多个CPU通过同一个BUS存取数据,导致BUS拥堵
2:多个CPU访问同一块内存获取数据,导致内存资源争抢严重

这个问题可以通过以下这种架构来解决,即分片,多个内存,分配给一个或多个CPU使用,如果某CPU需要访问其他CPU的内存的话则可以通过router路由完成,这种架构叫做非一致性内存访问架构,如下图:

在这里插入图片描述

1.2:线程创建过程分析

线程最终的创建还是需要依赖于底层的操作系统,所以Java中的线程,有三个层面的概念,Java代码层面,jvm层面,操作系统层面,参考下图:

在这里插入图片描述

解释如下:

1:创建完Thread对象后调用其start方法
2,3:JVM创建JVM层面的JavaThread对象,然后通过操作系统创建操作系统层次的OSThread对象
4:在JVM的虚拟机栈上给该线程分配内存
5:给该线程分配TLAB
6:操作系统调度执行线程,自动执行thread的run方法逻辑
7:run方法执行完毕,操作系统结束线程

1.3:一个简单的例子

1.3.1:什么是守护线程

先看一个问题,jvm什么时候会退出,看老外写的原版表述The Java Virtual Machine exits when the only threads running are all daemon threads.,即当jvm中运行的线程都是守护线程时,jvm会退出,如下:

public class NoneDaemonThreadTest {

    public static void main(String[] args) {

        Thread noneDaemonThread = new Thread(() -> {
            while (true) {
                try {
//                    Thread.sleep(1);
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("hi,i am still running...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        noneDaemonThread.start();

        try {
//            Thread.sleep(5);
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main thread exits...");

    }
}

运行:

在这里插入图片描述

如何实现在main函数执行完毕后让jvm退出呢?只需要设置setDaemon(true),就行了,如下:

在这里插入图片描述

运行,如下:

在这里插入图片描述

当然我们也可以添加一个jvm的钩子来确定jvm确实是退出了,如下:

在这里插入图片描述

那么守护进程有什么用呢?如果我们希望执行一些清理,信息收集等类型的工作时,但又不希望因为其存在而影响jvm的正常退出,因为当没有其他的非守护进程存在时,其存在也就没有意义了,最典型的,比如GC线程,就是守护线程,其是为了清理因为非守护线程运行而生成的对象而存在的。如果是工作中有类似的需求,我们也要尽量使用非守护进程,不要使用守护进程,避免因为其影响JVM的正常退出。

1.4:Thread和Runnable什么关系❓

我们创建线程的时候可以给java.lang.Thread一个java.lang.Runnable类一个参数,像这样:

new Thread(new Runnable() {
    @Override
    public void run() {
    }
})

也可以直接继承java.lang.Thread,像这样:

new Thread() {
    @Override
    public void run() {
        super.run();
    }
}

可以这么写的原因是java.lang.Thread类是java.lang.Runnable接口的实现类,如下:

public
class Thread implements Runnable {}

JVM在执行run方法时,会优先执行thread类本身的run方法,如果thread本身没有实现run方法,会执行构造函数中Runnable接口子类的run方法。

2:Thread常用属性和方法

2.1:name属性

线程名称,平时么有用,但是当排查导致问题的线程时会比较有用。

2.2:deamon属性

是否为守护线程,true是,false不是。守护线程本文前面内容已经分析过了。

2.3:Runnable target属性

只能通过Thread构造函数传入。

2.4:start方法

启动线程,之后进入可运行状态,等待操作系统调度执行。

2.5:wait/notify方法

先看个图:

在这里插入图片描述

1:如果线程执行时,被锁阻塞,则进入同步队列
2:如果线程正常获取锁,在同步代码块内部调用对象的wait方法,则该线程进入对象的等待队列
3:如果线程正常获取锁,在同步代码内部调用对象的notify/notifyAll方法,则等待队列线程进入到同步队列,待锁释放后争抢锁
4:如果线程正常获取锁,并且同步代码块正常执行完毕,则同步队列中的线程争抢锁,最终哪个线程获取到锁,不确定

调用某对象wait方法,必须持有该对象的对象锁,调用后当前线程进入waiting状态,并且进入对象的等待队列,等待notify/notifyAll方法唤醒,执行后释放对象锁,进入对象的等待队列,等待notify/notifyAll方法唤醒。另外调用wait时如果线程没有持有该对象的对象锁则会抛出IllegalMonitorException,notify/notifyAll方法如果是没有持有当前对象的对象锁,同样会抛出java.lang.IllegalMonitorException,这点使用的时候要特别小心。

锁对象和线程可参考下图:

在这里插入图片描述

wait方法会让出CPU,并释放锁,不同于sleep,sleep只会让出CPU时间片,但并不会释放锁,所以说其他线程依然无法进入同步代码块

2.6:join方法

底层执行的wait方法,如下:

public final synchronized void join(long millis)
    throws InterruptedException {
        ...
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                ...
                wait(delay);
            }
        }
    }

假定线程A在线程B中执行join方法,即A.join,则线程B会进入到线程A的等待队列,在线程A执行完毕后,底层会自动调用线程A的notifyAll方法,即A.notifyAll,则线程B从线程A的等待队列进入到同步队列,进而获取锁继续执行。参考如下代码。

package dongshi.daddy;

import java.util.concurrent.TimeUnit;

public class Huohuo {
    public static void main(String[] args) throws Exception {
        Thread task = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable content...");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        task.start();
        task.join();
        System.out.println("main run end...");
    }
}

运行,主线程等待task线程执行完毕才继续执行,如下:

runnable content...
main run end...

执行过程分析:

在这里插入图片描述

2.7:本地静态currentThread

获取当前上线文正在执行的线程。

3:线程状态分析

本部分看下线程可能的状态,以及会改变线程状态的操作。

3.1:线程可能的状态

在这里插入图片描述

3.2:会改变线程状态的操作

3.2.1:sleep

如:

void fn() {
    ...
    Thread.sleep(time);
    ...
}

让出CPU,线程从RUNNABLE装填变为TIME_WATING状态,作用是给其他线程使用CPU的机会,当睡眠到时则自动进入RUNNABLE状态,等待操作系统调度。需要注意该操作不会释放对象锁。

3.2.2:yield

如:

void fn() {
    ...
    Thread.yield();
    ...
}

Thread类的静态本地方法,会让出CPU时间片,从RUNNING状态重新进入就绪状态,即会立即开始争抢CPU时间片。

3.2.3:join/join(time)

如:

void fn() {
    ...
    Thread t = new Thread();
    t.join();
    ...
}

在当前线程调用某些线程t的join方法,则当前线程会等待线程t执行完毕之后,由底层调用notify方法唤醒,重新进入到可运行状态。不会释放当前线程的对象锁,但是会释放线程t的对象锁,因为底层调用的其实是t.wait();

3.2.4:wait/wait(time)

java.lang.Object类的本地方法,当还行obj.wait后,线程会释放obj的对象锁,并进入obj的等待队列。wait到时或者是有notify/notifyAll方法唤醒,重新变为就绪状态,开始争抢CPU时间片。

3.2.5:notify/notifyAll

唤醒在对象的等待队列中处于WAITING,TIMED_WAITING状态的线程,notify唤醒随机的一个,notifyAll唤醒所有的线程,进入就绪状态。

4:线程的中断异常处理

如果我们想要中断一个线程的执行,该怎么做?详细你首先会想到Thread的实例方法interrupt,但该方法真的可以吗?如下代码:

public class Huohuo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                System.out.println();
            }
        });
        t1.start();
        t1.interrupt();
    }
}

运行后你会发现,jvm进程依然在运行,也就是t1.interrupt();程序的执行对于t1线程毫无作用,其实该方法只有当执行了Object.wait,Thread.sleep(),Thread.join而进入到WATIING,TIMED_WAITING状态后,才会有效果,并且在线程的run方法内会抛出java.lang.InterruptedException,这是一个非运行时异常,需要try住,修改如下:

public class Huohuo {
    static Object lock1 = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                System.out.println();
                try {
                    lock1.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        System.out.println("中断线程t1");
        t1.interrupt();
    }
}

运行,如下:

中断线程t1

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at dongshi.daddy.Huohuo.lambda$main$0(Huohuo.java:10)
	at java.lang.Thread.run(Thread.java:748)

也就是说想要中断线程,必须先让想要中断的线程处于WAITING,TIMED_WAITING状态。具体可参考下图:

在这里插入图片描述

写在后面

参考文章列表

面试官: 谈谈什么是守护线程以及作用 ? 。

Java等待唤醒机制wait/notify深入解析 。

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

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

相关文章

Vintage的变体与解读应用

Vintage在风险管理中有很多变体,通常分为以下四类: 1.根据选取切片数据时间不一样,可划分为Month end(月末)与Cycle end(期末)两种。 说明: **Month end:**选取切片数…

基于自适应高斯混合的稀疏注释语义分割

文章目录 Sparsely Annotated Semantic Segmentation with Adaptive Gaussian Mixtures摘要本文方法GMM FormulationTraining with AGMM 实验结果 Sparsely Annotated Semantic Segmentation with Adaptive Gaussian Mixtures 摘要 稀疏注释语义分割(SASS&#xf…

给图片添加水印的最简单方法

给图片添加水印的最简单方法在数字化时代,图片广泛应用于社交媒体、网站、电商平台等各种场景中。然而,由于互联网分享的便捷性,图片的盗用问题也越来越严重。为了保护图片版权,给图片添加水印已经成为一个非常必要和重要的步骤。…

参与赢大奖!阿里云机器学习平台PAI助力开发者激发AIGC潜能

近年来,随着海量多模态数据在互联网的爆炸性增长和训练深度学习大模型的算力大幅提升,助力开发者一站式快速搭建文生图、对话等热门场景应用,阿里云机器学习平台PAI特推出AIGC加油包,为广大开发者加油助力激发AIGC潜能&#xff0c…

OJ管理员如何进行添加题目

OJ系统简介 Online Judge系统(简称OJ)是一个在线的判题系统。用户可以在线提交程序多种程序(如C、C) 源代码,系统对源代码进行编译和执行,并通过预先设计的测试数据来检验程序源代码的正确性。 一个用户提…

手把手教你进行Mysql条件查询操作

关注“Java架构栈”微信公众号,回复暗号【Java面试题】即可获取大厂面试题 从今天开始,健哥会带各位小伙伴开始学习数据库技术,这是Java开发中必不可少的一部分内容,也是非常重要的技术。本系列教程由浅入深,全面讲解数…

CentOS 停服倒计时,如何打造安全好用的 Linux 系统?

导读近年来,操作系统在国内的讨论极其热烈,也备受各方关注,操作系统在开发者圈中的重要性越来越高。毋庸置疑,对于众多的开发者来说,选择合适的操作系统对于开发效率、代码质量和个人发展都有着非常重要的影响。CSDN 作…

【Protobuf速成指南】oneof类型的使用

文章目录 2.3 oneof 类型一、基本认识二、 3.2 oneof相关函数三、Contact2.3 改写 2.3 oneof 类型 本系列文章将通过对通讯录项目的不断完善,带大家由浅入深的学习Protobuf的使用。这是Contacts的2.3版本,在这篇文章中将带大家学习Protobuf的 oneof 语关…

医院监控4大难点如何破解?这个方法太顶了

在医院环境中,许多重要的设备对于病人的诊断、治疗和监护至关重要。为了确保这些设备的正常运行和安全性,动环监控系统可以发挥关键作用。 客户案例 四川某大型综合医院引入了动环监控系统来监控其重要设备,如手术室设备、监护设备和医疗影像…

【Android自动化测试】Ui Automator技术(以对QQ软件自动发说说为例)

文章目录 一、引言二、了解(Android官方文档)1、UiDevice 类2、UI Automator API3、UI Automator 查看器 三、使用1、依赖2、代码 一、引言 描述:UI Automator 是一个界面测试框架,适用于整个系统上以及多个已安装应用间的跨应用…

使用Unity开发一个游戏类型的区块链 [独立区块链]

Arouse Blockchain [Unity独立区块链] 这是一个学习性质的项目,使用了Unity进行独立区块链游戏的开发。使用此项目,将默认你有一定的Unity基础,如果你是Unity小白,可以先学习Unity,B站有大量的教材。 内容列表 项目的状…

【RV1126】按键中断--使用输入子系统事件方式

文章目录 选择GPIO修改设备树修改驱动源码probe:增加外部中断以及定时器服务函数。命令行测试中断确定按键的输入子系统的event使用hexdump命令测试按键实际效果 测试应用程序完整的驱动代码 我使用的是荣品的pro-rv1126。 选择GPIO 结合原理图 决定使用UART1接口 …

SIFT算法分析

引言 尺度不变特征变换(SITF)是传统机器学习算法当中比较难的一个算法,步骤也相对其它传统机器学习算法要更多一些。 使用场景 以上是一个图像拼接的过程,两张图像拼接成一张图,有时候由于镜头限制,不能一…

基于Google breakpad编译构建和使用案例

Googlepad是一款用于程序崩溃时自动生成转储文件(.dmp)的可跨平台开源库。 1.Googlepad代码下载地址: git clone https://chromium.googlesource.com/breakpad/breakpad 如果翻不了墙可以在gitee上下载breakpad模块。 2.Googlepad采用gyp来…

C语言之实用调试技巧(2)

如何写出好(易于调试)的代码 优秀的代码: 1. 代码运行正常 2. bug很少 3. 效率高 4. 可读性高 5. 可维护性高 6. 注释清晰 7. 文档齐全 常见的 coding 技巧: 1. 使用assert 2. 尽量使用const 3. 养成良好的编码风格 4. …

Linux防火墙学习笔记14

firewalld是什么? Firewalld属于动态防火墙,是CentOS7系统中用于对netfilter内核模块用户空间管理工具。 Firewalld仅仅代替了iptables service部分,其底层还是使用iptables作为防火墙规则管理工具。 Firewalld中zone概念及作用&#xff1…

盖茨预言AI助理成标配,AI+RPA打破AI准入高门槛!

根据微软联合创始人比尔盖茨的预测,未来顶级的人工智能公司将会开发一种全新的“个人AI助理”。比尔盖茨表示,“个人AI助理”将会具有出色的功能,可以改变人们的生活方式以及工作方式。无论哪一家公司能够赢得AI助理竞争先机,都会…

耗时半月,终于把CSDN上的Java面试八股文整理成了PDF合集(Java基础+Redis+MySQL+多线程与高并发+JVM+分布式+Spring+微服务)

大家好,最近有不少小伙伴在后台留言,近期的面试越来越难了,要背的八股文越来越多了,考察得越来越细,越来越底层,明摆着就是想让我们徒手造航母嘛!实在是太为难我们这些程序员了。 这不&#xf…

Android:OKHttp

特点 支持HTTP2/SPDYSocket自动选择最好路线,并支持自动重连拥有自动维护的Socket连接池,减少握手次数拥有队列线程池,轻松写并发拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩)实现基于Headers的缓存策略 基…

JDBC Apache—DBUtils 详解(通俗易懂)

目录 一、前言 二、Apache—DBUtils的引入 1.传统使用ResultSet的缺点 : 2.改进方法 : 3.改进方法的模拟实现 : 三、Apache—DBUtils的使用 1.基本介绍 : 2.准备工作 : 3.DBUtils查询(DQL) : 4.query方法源码分析 : 5.DBUtils处理(DML) : 四、总结 一、前言 第六节…