[Java EE] 多线程(一) :线程的创建与常用方法(上)

news2024/11/26 12:20:05

1. 认识线程

1.1 概念

1.1.1 什么是线程

⼀个线程就是⼀个"执⾏流".每个线程之间都可以按照顺序执⾏⾃⼰的代码.多个线程之间"同时"执⾏
着多份代码.

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

1.1.2 为什么要有线程

首先,“并发编程"是"刚需”.

  • 单核CPU的发展遇到了瓶颈.要想提高算例,就需要多核CPU.而并发编程能更充分利用多核CPU
    资源
    .可以把一个任务拆解为多个部分,分配给不同的CPU核心来完成.
  • 其次虽然多进程也可以实现并发编程,但是线程比进程更加轻量.
    • 创建线程比创建进程快.
    • 销毁线程要比销毁进程快(上述两方面的原因都是因为每次创建进程都会分配系统资源,这种操作的开销比较大,而线程就可以对资源进程沿用)
    • 调度线程比调度进程快.(通过PCB中的线程调度属性来支持)

1.1.3 PCB与线程

严格意义上来说,一个PCB是描述一个线程的,而进程是若干个PCB合起来描述的.

  1. pid:每个线程都不同.(线程id)
  2. 内存指针:同一进程相同,线程之间不一样.
  3. 文件描述符表:同一进程相同,线程之间不一样.(由于同一进程调度的系统文件和使用的内存资源都是一样的,所以2,3一样)
  4. 线程调度属性:由于它是支持线程调度的一个属性,所以很明显不同线程之间一定不一样,他是每个线程独有的属性.
  5. tgid:同一进程中的线程之间都是一样的.(进程id)

我们拿一个"合租"的例子来说明上述问题
比如我们合租一间房子,客厅,卫生间,厨房等空间都是公共区域,相当于上述PCB中的内存指针和文件描述符表还有tgid,每个线程之间共享着内存空间和系统文件.每个卧室之间是每个人独有的空间,每个人的房间都不一样,就像上述的pid,线程调度属性.
在这里插入图片描述

1.1.4 线程与进程的区别(重点面试题,面试必考)

  • 进程包含线程,每个进程至少有一个进程存在,就是主线程.
  • 进程和进程之间不共享内存空间和文件资源,即它们的内存指针和文件描述符表不一样.而同一进程的线程之间共享内存空间和文件资源.
  • 进程是资源分配的基本单位(分配内存空间和文件资源),线程是执行调度的基本单位(创建线程调度属性).
  • 一个进程崩溃不会影响到其他进程,一个线程崩溃可能会影响其他进程,连同其他线程一起崩溃.

[举例]
下面有请我们的助教,蔡徐坤老师.
全民制作人们大家好,我是练习时长两年半的个人练习生cxk,喜欢Java,数据结构,数据库,music

  1. 坤坤要进一间房中吃只因,创建了一个带有一个主线程的进程:
    在这里插入图片描述
  2. 由于房间中的只因实在太多了,有100只,于是便分配到两个房间来有两个坤坤吃:
    在这里插入图片描述
  3. 但是由于在盖一间房(创建一个新的进程)的开销实在是太大了,所以我们让好多个坤坤(好多个线程)进同一个房间吃只因.(创建好多个线程),这样就会节省开销.
    在这里插入图片描述
  4. 但是此时,有两个坤坤产生了冲突,它们在抢一只只因大腿,它们都想吃.
    在这里插入图片描述
  5. 此时,旁边的坤坤来劝架(鸡哥算了算了),劝住了.大家继续吃只因.(进程继续运行)
    在这里插入图片描述
  6. 如果没劝住,就直接掀桌(╯‵□′)╯︵┻━┻了,大家谁都别吃了,进程也就挂掉了.
    在这里插入图片描述

1.2 创建多线程

注:下面在主方法中创建的线程属于主线程,在程序执行之后会自动启动线程.建议看完后面的1.3的一部分内容之后再来看这里.run()方法,start()方法,构造,sleep()方法都在后面.

  1. 通过继承Tread类,并在主线程创建线程对象.
public class MyTread extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("hello tread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {//注意调用sleep方法需要抛出异常
                throw new RuntimeException(e);
            }
        }
    }

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

  1. 通过实现Runnable接口,并在主线程为Tread的构造方法传入这个类的引用来创建线程对象.
public class MyTread1 implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("hello tread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyTread());
        thread.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  1. 通过匿名内部类来创建线程对象.第一种是直接重写Tread接口中的方法,第二种是给Tread类的构造方法传入Runnable接口,并重写Runnable中的方法.
public class MyTread2 {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (true){
                    System.out.println("hello tread");
                    try {
                        Thread.sleep(1000);//Tread类中的一个static方法
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        thread.start();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

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

  1. 通过创建labda表达式,这是对run方法的一种平替版本.
public class MyTread4 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true){
                System.out.println("hello tread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

2. Tread类及其常见方法

Thread类是JVM⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的Thread对象与之关
联。⽤我们上⾯的例⼦来看,每个执⾏流,也需要有⼀个对象来描述,类似下图所⽰,⽽Thread类的对象就是⽤来描述⼀个线程执⾏流的,JVM会将这些Thread对象组织起来,⽤于线程调度,线程管理.
在这里插入图片描述

2.1 Tread的常见构造方法

方法说明
Tread()创建线程对象
Tread(Runnable target)使用Runnable对象来创建线程
Tread(String name)创建线程对象并命名
Tread(Runnable target,String name)使用Runnable对象来创建线程并命名
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

2.2 Tread的常见属性

属性获取方法
idgetId()
名称getName()
优先级getPriority()
状态getState()
是否是后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

拓展:把一个线程设置为后台线程的方法是线程引用.setDaemon(true).

  • id是线程的唯一标识,不同的线程不会重复
  • 名称可以通过jconsole看到.
  • 状态表示线程当前所处的⼀个情况,下面我们会进⼀步说明.
  • 优先级高的线程理论上来说更容易被调度到.
  • 关于后台线程,我们要多唠几句:
    首先什么是前台线程,这和我们平时所知道的前台不一样,这里指的前台线程是,前台线程运行不结束,Java程序永远不会结束,后台线程是,后台即使在运行,只要前台线程全部结束,也不可以阻止Java程序结束,当然后台线程的结束也不可以对Java程序的执行产生任何影响.(如gc线程,就是Java垃圾回收线程,是周期性持续执行的后台线程)

我们下面举一个代码的例子:

public class Demo0 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("tread");
        });
        thread.setDaemon(true);//把tread线程设置为后台线程
        thread.start();
        System.out.println("main is terminate");
    }
}

运行结果:
在这里插入图片描述
这里我们可以看到,只打印了最后main线程的一句话,由于在tread设置为了后台线程,而且在线程中sleep了1s,tread还没来得及反应,前台线程main线程就已经结束了,整个进程就结束了,后台的tread也不得不结束.

我们下面通过一个实际生活中的例子-----吃酒席来说明:

  1. 今天吃酒席,有马化腾,马云,还有一些小喽啰.
    在这里插入图片描述
    由于马云和马化腾都是大老板,掌管者整个酒席的节奏,一桌酒席是否结束,由他们说了算.它们就是前台线程,掌管者整个进程.
  2. 马云和马化腾掌管者整个酒席的节奏,它们都说结束才可以结束,要是有一个没喝好,就不可以结束.
    在这里插入图片描述
    酒席结束了,小喽啰不走也得走了.
    在这里插入图片描述
  3. 突然有一个小喽啰实在喝不动了,提前溜了.但是酒席不可以结束,马爸爸和马爹爹还没有喝好,接着喝,可见后台进程的结束并不影响进程是否结束.
    在这里插入图片描述
  • 是否存活,线程中的线程引用是否被gc回收,这里不可以理解为线程是否还在执行,两者有一定的区别.
  • 线程终端问题,后序介绍.

[未完待续…]

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

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

相关文章

十大排序——9.桶排序

这篇文章我们来介绍一下桶排序 目录 1.介绍 2.代码实现 3.总结与思考 1.介绍 桶排序和计数排序一样,都不是基于比较进行排序的。 下面通过一个例子来理解一下桶排序吧。 首先,给你一个无序数组[ 20,18,28,66,25,31,67,30 ],然后&#…

CANoe中LIN工程主节点的配置(如何切换调度表)

1:前置条件 1)工程已经建立,simulation窗口已经配置好(包括且不限于通道mappin好,数据库文件已经添加) 2)我已系统自带sampleCfg工程,作为例子。如下图 2 :主节点的配置…

普发Pfeiffer CCR263 CCR272 CMR261 CMR273 PBR260 IMR265 TPR265 使用说明手侧

普发Pfeiffer CCR263 CCR272 CMR261 CMR273 PBR260 IMR265 TPR265 使用说明手侧

PhpStorm2024安装包(亲测可用)

目录 一、软件简介 二、软件下载 一、软件简介 PhpStorm是由JetBrains公司开发的一款商业的PHP集成开发环境(IDE),深受全球开发人员的喜爱。它旨在提高开发效率,通过深刻理解用户的编码习惯,提供智能代码补全、快速导…

线程终止操作

关于线程的终止,我们有两种方法来实现 1.使用一个标志位 private static boolean flag true;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() -> {while (flag) {System.out.println("你好");}});//…

【C语言】带你完全理解指针(五)练习

复习一下对数组名的理解 数组名的理解 数组名是数组首元素的地址 但是有2个例外: 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节 2. &数组名,这里的数组名表示整个数组&#xff…

《六》输入组控件InputWidget---Combo Box

一、Qt 输入组控件(Input Widgets) Qt Input Widgets是一组用户界面元素,用于输入和显示文字和数字等的数据。这些小部件可以组成各种不同的表单和对话框,用户可以使用这些小部件与程序交互。 以下是Qt Input Widgets的一些常见小部件&…

L2-3 完全二叉树的层序遍历

完全二叉树的层序遍历 一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是完美二叉树。对于深度为 D 的,有 N 个结点的二叉树,若其结点对应于相同深度完美二叉树的层序遍历的前 N 个结点,这样的树就是完全…

跟着Datawhale重学数据结构与算法

数据结构和算法之前学过,现在跟着Datawhale重学一下,就当是监督自己学习,重新拾起来养成一个好的习惯,以后可以一直坚持下去。 开源链接:【 教程地址 】【电子网站】 首先: #mermaid-svg-Cdr3rn9fGCVAiKS…

STM32-ADC(独立模式、双重模式)

ADC简介 18个通道:外部信号源就是16个GPIO回。在引脚上直接接模拟信号就行了,不需要侄何额外的电路。引脚就直接能测电压。2个内部信号源是内部温度传感器和内部参考电压。 逐次逼近型ADC: 它是一个独立的8位逐次逼近型ADC芯片,这个ADC0809是…

C语言 | 自定义类型:联合和枚举

目录: ----前言 1. 联合体 1.1 联合体类型的声明 1.2 联合体的特点 1.3 相同成员的结构体和联合体对比 1.4 联合体大小的计算 1.5 联合的使用 1.6联合体的练习 2. 枚举 2.1 枚举类型的声明 2.2 枚举类型的优点 2.3 枚举类型的使用 --前言: c语言中内…

String类的几个常用方法

描述 以下程序演示了String类的几个常用方法,包括比较字符串、取得字符串长度、拆分字符串、获取子串、字符串转换成字符数组、获取字符串位置、替换字符串等方法。 案例 public class StringTest {public static void main(String[] args) {//比较字符串String …

阿赵UE学习笔记——28、粒子系统Niagara简介

阿赵UE学习笔记目录 大家好,我是阿赵。   继续学习虚幻引擎的使用。这次开始学习粒子系统的使用。 一、Cascade系统 在介绍UE5的Niagara系统之前,必须先介绍一下旧版本的粒子系统。   在UE4的时候,虚幻引擎的粒子系统叫做Cascade&#x…

ubuntu 使用conda 创建虚拟环境总是报HTTP错误,转换多个镜像源之后仍报错

最近在使用Ubuntu conda创建虚拟环境时,总是报Http错误,如下图所示: 开始,我以为是conda 镜像源的问题,但是尝试了好几个镜像源都不行,还是报各种各样的HTTP错误。后来查阅很多,总算解决了。解…

数学建模--深入剖析线性规划(模型全方位解读+代码分析)

1.简介 (1)线性规划三要素 (2)模型适用赛题 2.典例讲解 (1)问题分析 目标函数是净收益尽可能大,风险尽可能小; 约束条件是交易费的分段函数,以及每一笔投资都是非负数&am…

【精读文献】Scientific data|2017-2021年中国10米玉米农田变化制图

论文名称:Mapping annual 10-m maize cropland changes in China during 2017–2021 第一作者及通讯作者:Xingang Li, Ying Qu 第一作者单位及通讯作者单位:北京师范大学地理学部 文章发表期刊:《Scientific data》&#xff08…

hot100 -- 链表(中)

不要觉得力扣核心代码模式麻烦,它确实比不上ACM模式舒服,可以自己处理输入输出 只是你对 链表 和 return 的理解不到位 👂 ▶ 屿前世 (163.com) 👂 ▶ see you tomorrow (163.com) 目录 🎂两数相加 🚩删…

android不同版本(支持>10)获取当前连接的wifi名称

1、AndroidManifest.xml 配置权限 <uses-permission android:name"android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name"android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name&q…

Rust腐蚀服务器清档多教程

Rust腐蚀服务器清档多教程 大家好我是艾西&#xff0c;一个做服务器租用的网络架构师。上期教了大家怎么搭建服务器以及安装插件等那么随着大家自己架设服或是玩耍的时间肯定会有小伙伴想要去新增开区数量或是把原本的服务器进行一些调整等&#xff0c;那么今天主要聊的就是怎…

Cohere Command R+:企业级可扩展大模型

Command R 是一种最先进的 RAG 优化模型&#xff0c;旨在处理企业级工作负载&#xff0c;并首先在 Microsoft Azure 上提供 今天&#xff0c;我们推出了 Command R&#xff0c;这是我们最强大、可扩展的大型语言模型 &#xff08;LLM&#xff09;&#xff0c;专为在实际企业用…