【Java系列】详解多线程(二)——Thread类及常见方法(上篇)

news2024/11/23 17:13:27

个人主页:兜里有颗棉花糖
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创
收录于专栏【Java系列专栏】【JaveEE学习专栏】
本专栏旨在分享学习Java的一点学习心得,欢迎大家在评论区交流讨论💌

目录

  • 一、前文回顾
  • 二、创建线程的几种方式。
    • 继承Thread类
    • 实现runnable方法
    • lambda表达式
  • 三、Thread类及常见方法
    • Thread类的常见构造方法
    • Thread类的属性

一、前文回顾

我们先来回顾一下线程与进程之间的联系。

我们知道多进程可以帮助我们完成并发编程,即可以把多个cpu核心充分利用起来以完成同时执行多任务的场景。但是进程有一个问题就是进程的创建和销毁的开销是比较大的,如果我们需要频繁的创建和销毁进程的话,那么多进程这种方式就是比较低效的。所以就衍生出了轻量级进程——线程。线程之所以比进程更轻量级主要有两方面的原因

  • 一方面是线程共享进程的资源,不要忘记,线程是进程的一部分,线程可以共享那些属于进程的资源,但是进程之间是相对独立的,线程创建时不需要像进程那样需要分配独立的资源,因此线程创建和销毁的开销会比进程小很多
  • 另一方面线程调度切换要比进程调度切换要快上很多,由于多线程使用的调度算法比单进程要复杂很多,加上线程对系统资源的共享(线程在同一个进程内共享资源,线程切换只需要切换线程的上下文,而不需要切换整个进程的上下文;而相比之间进程切换需要保存和恢复进程的所有上下文信息,包括内存映像、打开的文件、进程状态等。),所以线程调度之间的切换要比进程调度的切换快上很多,也更容易实现多任务的处理。

线程在同一进程内共享内存资源,所以当进程创建完成时,线程已经可以访问和共享这些内存资源(主线程是在进程创建时默认创建的一个线程)。如果后续需要我们创建其它的线程,我们就可以重用之前的字眼就可以了。

进程包含了线程,一个进程内部至少有一个线程,进程是系统中分配资源的基本单位,而线程是cpu调度执行的基本单位。我们可以这样说线程的引入最主要的就是为了实现更高效的并发处理和资源共享。

由于线程可以共享同一进程内的内存资源,因此资源分配的主要任务是为进程分配足够的内存空间,对线程的资源分配比在进程级别上的资源分配要少得多。

现在问题来了,一个进程内既然可以存在多个线程,同时线程可以共享同一个进程内的资源,这也意味着这多个线程之间的相互干扰会比较大(这一点就不如进程之间那样相对独立一些,一个进程挂掉知道后不会对其它进程产生影响。),一旦某个出现异常之后就有可能导致整个进程都会挂掉,这当然会对其它线程产生影响。另外,多个线程去访问同一块公共资源的时候也可能会出现冲突带来线程安全的问题。

所以,总的来说使用多线程的方式完成任务比多进程的方式完成任务会有优势,但同时存在一些缺点。但尽管如此,我们依然可以利用多线程的方式来很好的完成并发编程的任务。

好了,友友们,回顾到此结束,接下来我们一起学习新的内容吧!!!

二、创建线程的几种方式。

Java标准库中,提供了Thread()类来表示线程,同时Java创建线程的方式有很多种。最常见的两种写法:继承Thread()类重写Runnable方法()

继承Thread类

方式一:通过继承thread类来重写run方法是一种创建线程的方式(这里我们需要自己创建一个类来继承thread方法);方式二:还有一种方式我们也可以创建线程:基于匿名内部类的形式来继承thread类,并重run方法。

现在我们通过匿名内部类的方式来创建线程(基于匿名内部类的形式来继承thread类,并重写run方法,即方式二):

代码如下:

public class Demo03 {
    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();
    }
}

解释:上述代码中我们创建了一个子类,这个子类(此类就是我们说的匿名内部类)继承自Thread类,同时我们在这个子类重对run方法进行了重写。
另外我们还创建了该子类(也就是之前说的匿名内部类)的实例,用引用t类指向这个实例。
最后就是通过引用t来调用start方法来调用系统API,再从内核中把线程创建出来。
在这里插入图片描述
最后我们来看一下运行结果:
在这里插入图片描述

实现runnable方法

方式三通过实现runnable来重写run方法(这里是自己创建一个类)的方式可以创建一个线程。方式四:另外我们这里依然可以用其它方式来创建一个线程:即基于匿名内部类的形式来实现runnable并重写run方法。

现在我们通过匿名内部类的形式来实现runnable并重写run方法(即方式四)来创建线程,代码如下:

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

解释,上述代码中,我们创建了runnable的子类,并重写了run方法,然后创建出了runnable的子类的实例,把这个实例传给Thread的构造方法。

lambda表达式

我们还可以通过lambda表达式来表示run方法的内容,从而创建出线程。

代码如下:

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

什么是lambda表达式:lambda表达式本质上就是一个匿名函数,主要可以用来作为回调函数来进行使用。
先来回顾一下回调函数吧:回调函数不需要我们自己去进行调用,因为回调函数会在特定的时机自动地被调用。
此时的回调函数就是在线程创建成功之后才会真正执行。

关于线程的创建这里,也有其它的方式,比如基于callable的方式创建线程,基于线程池的方式创建线程。

三、Thread类及常见方法

Thread类是JVM用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread对象与之关联。我们对线程的各种操作都是根据Thread类进行展开的。

Thread类的常见构造方法

这里我们可以参照Java官方文档:https://docs.oracle.com/javase/8/docs/api/index.html
在这里插入图片描述

在这里插入图片描述

如下图的代码中,我们给线程起了个名字mythread
在这里插入图片描述
我们可以通过JVM中的jconsole.exe来看到这个线程(mythread):
在这里插入图片描述
现在问题来了,为什么我们没有看到主线程(main)呢?在上述代码中,主线程创建并启动了一个新线程,而新线程负责打印"hello thread"的消息。由于新线程的循环中有一个线程休眠的操作(Thread.sleep(1000)),所以您将会在不同时间间隔内看到"hello thread"的输出。但这个输出是由新线程负责的,而不是主线程。
所以,我们没有看到主线程的原因是主线程没有显示任何输出或执行其他代码来表明它的存在。它仅仅完成了创建和启动新线程的任务,然后退出。
对于主线程来说,main方法就是主线程的入口;而对于其它线程来说,lambda或者run就是线程的入口。所以执行完线程的入口函数,线程就算是结束了。

Thread类的属性

下面是Thread类的几个常见属性:

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDasmon()
是否存活isAlive()
是否被中断isInterrupted()
  • ID:这里是JVM给线程设定的身份标识。对于一个线程来说,身份标识可以有好多个,就像一个人有大名也有小名。比如JVM给某一线程设定了一个身份标识,phread库(系统给开发人员提供的操作线程的API)也有一个线程的身份标识,内核中还有一个线程的身份标识,这几个身份标识之间相互独立互不干扰。
  • 名称:设置线程名称方便我们知道这个线程是干什么用的,同时也方便我们去进行调试。
  • 状态:Java中的线程状态和操作系统中的有所区别,Java中的线程状态更加细化。
  • 优先级:关于获取、设置线程的优先级其实并没有太大意义,因为系统内核进行线程调度的速度极快(线程调度是由系统内核负责的),快到我们根本无法感知,所有我们一般使用默认的线程优先级即可。
  • 是否是后台线程:后台线程又称为守护线程,后台线程不会影响线程结束;而前台线程会影响线程结束,如果前台线程没有执行完的话,进程是不会结束的;一个进程中如果所有的前台线程都执行完此时进程退出,如果此时依然存在后台线程没有执行完的话,后台线程依然会随着进程的退出而退出。我们创建的线程默认是前台线程。如下代码进行演示:
// 这里我们创建的线程是前台线程
public class Demo07 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(true) {
                System.out.println("hello thread!!!");
            }
        });
        t.start();
    }
}

// 这里将我们创建的线程设置为了后台线程
public class Demo07 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(true) {
                System.out.println("hello thread!!!");
            }
        });
        t.setDaemon(true);
        t.start();
    }
}

当我们把创建的线程设置为后台线程之后,程序运行起来之后就会立即结束。原因:由于我们把自己手动的线程创建成了后台线程,所以此时就只剩下main主线程了,而main线程这里不需要执行代码,所以执行时间极短main线程(前台线程)就结束了,而我们设置的后台线程还没有来得及执行就随着进程的退出而退出了。

  • 是否存活(isAlive()):这里的存活指的并不是thread对象是否存活,而是指的thread对象(我们也可以称为线程对象)对应的线程(即系统内核中的线程)是否存活。Thread对象的声明周期并不是和系统内核中的线程的生命周期完全一致。一般来说都是先把thread对象创建好,然后手动调用start方法,此时内核才真正创建出线程。对于线程,当它的run方法执行完后,线程的生命周期也会自然结束,就算线程对象还存在于内存中。此时该线程会释放占用的资源并进入死亡状态;还有另外一种情况就是线程对象生命周期的结束(即没有引用指向该线程对象了),此时线程也会结束,这种情况下,线程的run方法执行与否并不影响线程的结束状态。垃圾回收器会在适当时候回收无引用的线程对象,并释放相关资源。

好了,本文到这里就结束了,希望友友们可以支持一下一键三连哈。嗯,就到这里吧,再见啦!!!

在这里插入图片描述

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

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

相关文章

Spring Cloud gateway - CircuitBreaker GatewayFilte

前面学习Spring cloud gateway的时候,做测试的过程中我们发现,Spring Cloud Gateway不需要做多少配置就可以使用Spring Cloud LoadBalance的功能,比如: spring:application:name: spring-gatewaycloud:gateway:routes:- id: path…

20231213给Ubuntu18.04.6LTS新加一块HDD机械硬盘

20231213给Ubuntu18.04.6LTS新加一块HDD机械硬盘 2023/12/13 22:50 rootrootrootroot-X99-Turbo:~$ cat /etc/issue Ubuntu 18.04.6 LTS \n \l sudo fdisk -l rootrootrootroot-X99-Turbo:~$ rootrootrootroot-X99-Turbo:~$ sudo fdisk -lu Disk /dev/sda: 2.7 TiB, 300059298…

传输层协议介绍(三次握手,四次挥手)

一.传输层协议介绍 1.TCP协议概念 ①面向连接网络协议 ②是指通信双方之间在进行通信之前要先建立连接。比如打电话,双方通话前要先建立连接。 ③TCP协议是面向连接的,可靠的进程到进程通信的协议,TCP提供全双工服务,即数据可…

计网 - 一台主机上最多只能保持 65535 个TCP 连接吗

文章目录 Pre问题分析单一IP的服务端单一IP的客户端 Pre 高性能网络编程 - 关于单台服务器并发TCP连接数理论值的讨论 问题 一台主机上只能保持最多 65535 个 TCP 连接,正确吗? 先说结论: 这个说法不对,我们分服务器和客户端分…

2.操作符详解

1.10进制转二进制方法 所以125的二进制就是1111101 2.2进制转8进制: 从2进制序列中右边最低位开始向左每3个2进制位换算为一个8进制位,剩余不够3个2进制位的直接换算 例:01101011转为01 101 011 即1 5 3 即8进制的153 还原回去的话: 将3化为011放最右边,5化…

【C++进阶篇】二叉搜索数

目录 前言: 以后我们要学map,set,AVL,红黑数所以必须要有二叉搜索数做铺垫 1、二叉搜索树概念 2.二叉搜索树操作 1.二叉搜索树的查找 a、从根开始比较,查找,比根大则往右边走查找,比根小则…

QEMU源码全解析 —— virtio(5)

接前一篇文章: 本文内容参考: 《趣谈Linux操作系统》 —— 刘超,极客时间 《QEMU/KVM》源码解析与应用 —— 李强,机械工业出版社 特此致谢! 上一回以virtio balloon设备为例概述了具体的virtio设备、virtio PCI代理…

Proxmox创建Windows虚拟机

文章目录 下载ISO安装文件上传 下载ISO安装文件 下载地址:https://www.xitongzhijia.net/ 也可去官网进行下载 上传 将下载的ISO文件上传到Proxmox 选择ISO文件进行上传 上传后再ISO镜像中可以看到安装文件 点击创建虚拟机 填写名称,不能填写中文 镜…

使用NCNN在华为M5部署Yolov5

使用NCNN在华为M5平板部署Yolov5 一、NCNN二、下载解压NCNN三、下载ncnn-android-yolov5工程四、下载Android Studio[前提已经配置了jdk版本]1、安装NDK、Cmske,这个必须要安装,2、安装Android 五、构建工程六、修改源码七、重新ysnc project八、安装APP…

18个非技术面试题

请你自我介绍一下你自己? 这道面试题是大家在以后面试过程中会常被问到的,那么我们被问到之后,该如果回答呢?是说姓名?年龄?还是其他什么? 最佳回答提示: 一般人回答这个问题往往会…

IDEA中工具条中的debug按钮不能用了显示灰色

IDEA中工具条中的debug按钮不能用了显示灰色 1. 问题描述 IDEA上的DEBUG按钮突然变成了灰色: 2. 解决办法 一通搜索,终于找到解决办法 点击 File -> Project Structure如下图操作 3. 重启,解决 4. 参考 https://www.cnblogs.com…

linux下sys目录与proc目录的作用

sys目录作用 在Linux系统中,/sys目录是一个特殊的虚拟文件系统(sysfs),用于提供对内核和设备的运行时信息的访问。它是在内核中运行的驱动程序和子系统的接口,可以用于获取和配置系统的硬件和内核信息。 以下是/sys目…

Kubernetes实战(九)-kubeadm安装k8s集群

1 环境准备 1.1 主机信息 iphostname10.220.43.203master10.220.43.204node1 1.2 系统信息 $ cat /etc/redhat-release Alibaba Cloud Linux (Aliyun Linux) release 2.1903 LTS (Hunting Beagle) 2 部署准备 master/与slave主机均需要设置。 2.1 设置主机名 # master h…

2023年12月14日 十二生肖 今日运势

小运播报:2023年12月14日,星期四,农历十一月初二 (癸卯年甲子月丙午日),法定工作日。 红榜生肖:羊、狗、虎 需要注意:牛、马、鼠 喜神方位:西南方 财神方位&#xff…

004 Windows NTFS文件夹权限

一、NTFS文件权限: NTFS(New Technology File System)是Windows NT内核的系列操作系统支持的、一个特别为网络和磁盘配额、文件加密等管理安全特性设计的磁盘格式,提供长文件名、数据保护和恢复,能通过目录和文件许可…

实操Nginx(4层代理+7层代理)+Tomcat多实例部署,实现负载均衡和动静分离

目录 前言 一、tomcat多实例部署 步骤一:先安装jdk,设置jdk的环境变量,验证是否安装完成(192.168.20.8) 步骤二:安装tomcat(192.168.20.18) 步骤三:安装tomcat多实例…

微服务中如何保证接口的安全性?[基于DDD和微服务的开发实战]

大家好,我是飘渺。如果你的微服务需要向第三方开放接口,如何确保你提供的接口是安全的呢? 1. 什么是安全接口 通常来说,要将暴露在外网的 API 接口视为安全接口,需要实现防篡改和防重放的功能。 1.1 什么是篡改问题…

【Linux】多线程编程

目录 1. 线程基础知识 2. 线程创建 3. 线程ID(TID) 4. 线程终止 5. 线程取消 6. 线程等待 7. 线程分离 8. 线程互斥 8.1 初始化互斥量 8.2 销毁互斥量 8.3 互斥量加锁和解锁 9. 可重入和线程安全 10. 线程同步之条件变量 10.1 初始化条件变…

k8s安装Ingress-Nginx

目前,DHorse(https://gitee.com/i512team/dhorse)只支持Ingress-nginx的Ingress实现,下面介绍Ingress-nginx的安装过程。 下载安装文件 首先,需要匹配Ingress-nginx版本和kubernetes版本。 在https://github.com/kubernetes/ingress-nginx可…

【UE5 Niagara】烟雾弹效果

效果 步骤 1. 新建一个工程,创建Basic关卡 2. 新建一个Actor蓝图,这里命名为“BP_SmokeBomb” 打开“BP_SmokeBomb”,添加一个静态网格体和一个发射物移动组件,静态网格体使用圆柱模型 选中发射物移动组件,设置初始速…