【JavaEE初阶】多线程(一)认识线程 线程的创建 Thread的用法

news2024/10/6 8:28:45

摄影分享!
在这里插入图片描述
在这里插入图片描述

文章目录

  • 认识线程(Thread)
    • 概念
    • 执行多线程编程
    • 创建线程的写法
      • 1.继承Thread,重写run
      • 2.实现Runnable接口
      • 3.使用匿名内部类,继承Thread
      • 4.使用匿名内部类,实现Runable
      • 5.使用Lambda表达式
    • Thread用法
    • Thread的几个常见属性
    • 启动一个线程-start()
    • 线程终止
    • 等待一个线程join()
    • 获取当前线程的引用
    • 休眠当前线程

认识线程(Thread)

概念

引入“进程”这个概念,主要是为了解决“并发编程”的问题。
其实,多进程编程,已经可以解决并发编程的问题了。但是由于进程太重(消耗资源多,速度慢)。创建,销毁,调度一个进程的开销都比较大。主要是重在了“资源分配/回收”上。所以线程应运而生。线程也叫做“轻量级进程”解决并发编程问题的前提下,让创建,销毁,调度的速度更快一些。线程通过把申请资源/释放资源的操作省去所以线程更

举个例子~
在这里插入图片描述
在以上方案中,显然,方案2比方案1的成本要小很多。场地和物流线都可以使用之前的。这就是多线程版本的方案。此时,只是搞第一套生产线的时候,需要把资源申请到位,后续再加新的生产线,此时就复用之前的资源即可。
**线程和进程之间的关系,是进程包含线程。**一个进程可以包含一个线程,也可以包含多个线程(不能没有)。只有第一个线程启动的时候,开销是比较大的,后续的线程开销就小了。同一个进程里的多个线程之间,公用了进程的同一份资源。(主要指的是内存和文件描述符表)
如果每个进程由多个线程了,每个线程是独立在CPU上调度的。(线程是操作系统执行的基本单位)
每个线程也有自己的执行逻辑(执行流)
在这里插入图片描述
一个线程也是通过一个PCB来描述的。一个进程里可能是对应一个PCB,也可能是对应多个PCB。在同一个进程里的PCB之间,PID是一样的。内存指针和文件描述符表也是一样的。

再举个例子~

在这里插入图片描述
什么时候会出现这种安全问题呢?
多个执行流访问同一个共享资源的时候。
线程模型,天然就是资源共享的,多线程争抢同一个资源(同一个变量)非常容易触发的。
进程模型,天然是资源隔离的,不容易触发,进行进程间通信的时候,多个进程访问同一个资源,就可能会出问题。
多线程会提高效率,但不如多进程安全。

执行多线程编程

本身关于线程的操作,操作系统提供API。
Java是一个跨平台的语言,很多操作系统的提供的功能,都被JVM封装好了。由于Java在不同的操作系统上,实现了不同版本的JVM,所以Java实现了跨平台。
Java操作多线程,最核心的类是Thread。

class MyTread extends Thread{
    @Override
    public void run() {
        System.out.println("hello run");
    }
}
public class TreadDemo {
    public static void main(String[] args) {
        Thread t = new MyTread();
        t.start();
    }
}

在这里插入图片描述

t.start();

中start创建了一个新的线程。start中没有调用run。而是创建了一个线程,由线程来执行run方法。
就是调用操作系统的API,通过操作系统内核创建新线程的PCB,并且要把执行的指令交给当前PBC。当PCB被调度到CPU上执行的时候,也就执行到了线程run方法中的代码了。
java有一个主线程main,通过t.start();主线程调用t.start创建出一个新的线程,新的线程调用run
run方法执行完毕,新的这个线程自然销毁。

import static java.lang.Thread.sleep;

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

在这里插入图片描述
start和run之间的区别:
start是真正创建了一个线程,线程是独立的执行流。run只是描述了线程要做什么。
可以使用jdk自带的工具jconsole查看当前的java进程中的所有线程。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
PCB不是“简称”是一个数据结构,体现的是进程/线程是如何实现,如何被描述的。

在这里插入图片描述
堆栈跟踪(调用栈):描述了当前方法的调用关系。

创建线程的写法

1.继承Thread,重写run

在这里插入图片描述

2.实现Runnable接口

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class TreadDemo2 {
    public static void main(String[] args) {
        //描述了一个任务
        Runnable runnable = new MyRunnable();
        //把任务交给线程来执行
        Thread t = new Thread(runnable);
        t.start();
    }
}

在这里插入图片描述

3.使用匿名内部类,继承Thread

public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            public void run(){
                System.out.println("hello");
            }
        };
        t.start();
    }
}

在这里插入图片描述

4.使用匿名内部类,实现Runable

public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
        t.start();
    }
}

在这里插入图片描述

5.使用Lambda表达式

public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("hello");
        });
        t.start();
    }
}

在这里插入图片描述
在这里插入图片描述

Thread用法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target,String name)使用Runnable对象创建线程对象,并命名

在这里插入图片描述
t为代码里的变量名,mythread是系统里的线程名。

Thread的几个常见属性

属性获取方法
IDgetId()
名称(构造方法中起的)getName()
线程状态getState()
优先级(可以设置,但没用)getPriority()
是否后台(守护线程)线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

说明:

isDaemon()
前台线程:会阻止进程结束,前台线程的工作没做完,进程无法结束。 后台线程:不会阻止进程结束,后台线程工作未完成,进程可以结束。
代码中手动创建的线程,默认都是前台的。main线程默认也是前台的。 可以手动使用setDaemon设置成后台线程。
在这里插入图片描述

isAlive()
在这里插入图片描述

在这里插入图片描述
如果t的run还没跑,isAlive就是false
如果t的run正在跑,isAlive就是true
如果t的run跑完了,isAlive就是false

在这里插入图片描述
运行结果:
在这里插入图片描述
两个线程在微观上可能是并行的(两个核心),也可能是并发的(一个核心)宏观上无法感知,我们所看到的就是随机执行。系统是**抢占式调度。**多线程代码,需要考虑很多种可能性!

启动一个线程-start()

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

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

调用start方法,才是真的在操作系统的底层创建出一个线程。

线程终止

线程终止,不是让线程立即停止。而是通知线程要停止了。但是至于线程是否停止了,取决于代码的具体写法。

举个列子: 我在打游戏,麻麻突然告诉我家里的酱油没了。让我去打酱油~我有以下几个选择:

  1. 停止游戏,立即去
  2. 打完这把,再去
  3. 假装没听见,不去

类比如下:

  1. 使用标志位来控制线程是否要停止。
    在这里插入图片描述
  2. 使用Thread自带的标志位来进行判断
public class ThreadDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        t.interrupt();
    }
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
interrupt会做两件事:

  1. 把线程内部的标志位(boolean)设置成true。
  2. 如果线程正在进行sleep,就会触发异常。把sleep唤醒~

sleep被唤醒后,会将刚才设置的标志位,再设置回false。

t线程忽略终止请求:
在这里插入图片描述

线程t立即响应终止请求:
在这里插入图片描述
稍后进行终止:
在这里插入图片描述

等待一个线程join()

线程是一个随机调度的过程。等待线程就是在控制两个线程的结束顺序。

在这里插入图片描述
本身执行完start之后,t和main线程就并发执行。main继续往下执行,t也会继续往下执行。但是此时使用了join,就导致main线程发生阻塞(block)。一直阻塞到t线程执行结束,main线程才会从join中恢复,才能继续往下执行。
t线程肯定比main线程先结束。
在这里插入图片描述
在这里插入图片描述

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)同理,但可以更高精度

获取当前线程的引用

方法说明
public static Thread currentThread();返回当前线程对象的引用

休眠当前线程

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throwsInterruptedException 可以更高精度的休眠

操作系统内核中:
就绪队列链表中的PCB都是“随叫随到”的就绪状态。
在这里插入图片描述

操作系统每次需要调度一个线程去执行,就从就绪队列中选择一个。
线程A调用sleep,A就会进入休眠状态,把A从上述链表中拿出来,放入另一个链表中(阻塞队列)。

阻塞队列链表中的PCB都是“阻塞状态”,暂时不参与CPU的调度执行。
在这里插入图片描述
一旦线程进入阻塞状态,对应PCB就进入阻塞队列了,此时就暂时无法参与调度了。

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

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

相关文章

C语言模拟银行排队叫号(链队)

一.队列 队列是一种具有先进先出(FIFO)特性的线性数据结构,它只允许在队列的两端进行插入和删除操作。队列的一端称为队尾(rear),另一端称为队头(front)。新元素总是插入在队列的队…

怎么把m4a转换成mp3,分享几个方法给大家!

录音文件中经常出现m4a后缀的音频格式,但通常只能在特定的音频播放器中播放。如果你想把m4a转换成mp3,下面是四种简单易行的方法,适用于Windows 10操作系统。 方法一:使用记灵在线工具转换m4a成mp3 工具地址:记灵在线…

elsticsearch入门

查看所有索引(表) 向索引(表)中添加数据: 自定义id添加数据: 自定义id添加数据:方式二 查询数据: 查询索引(表)中全部数据: 全量修改单条数据&…

File类与IO流

1. java.io.File类的使用 1.1 概述 File类及本章下的各种流,都定义在java.io包下。一个File对象代表硬盘或网络中可能存在的一个文件或者文件目录(俗称文件夹),与平台无关。(体会万事万物皆对象)File 能新…

【Leetcode -剑指Offer 22.链表中倒数第k个结点 -203.移除链表元素】

Leetcode Leetcode -剑指Offer 22.链表中倒数第k个结点Leetcode -203.移除链表元素 Leetcode -剑指Offer 22.链表中倒数第k个结点 题目:输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表…

数据结构(三)—— 哈希表

文章目录 一、哈希表积累1.1 哈希map1.2 哈希set 二、哈希表基础三、题3.1 242 有效的字母异位词3.2 349 两个数组的交集3.3 202 快乐数3.4 1 两数之和3.5 54 四数相加II 一、哈希表积累 什么时候想到用哈希法:当要需要查询一个元素是否出现过、判断一个元素是否出…

awvs安装批量扫描

文章目录 安装批量扫描 安装 1.2.AWVS下载   该工具可在官方网站下载,但免费下载的是14天试用版本。   官网下载:AWVS   百度云下载:百度云下载https://pan.baidu.com/s/1UO7GzL0CMemJ_TMQnHNOuA?pwdg1bm 提取码:g1bm 批…

浏览器状态同步和路由-前端路由和服务端路由原理

目录 前端路由和History API 浏览文境(Browser Context) 会话历史(Session History) History API history.go()切换当前会话,并不改变会话栈 history.back() history.go(-1) & history.forward() history.…

5.4、服务器编程基本框架和两种高效的事件处理模式

5.4、服务器编程基本框架和两种高效的事件处理模式 1.服务器编程基本框架2.两种高效的事件处理模式①Reactor模式②Proactor模式③模拟Proactor模式 1.服务器编程基本框架 模块功能I/O 处理单元处理客户连接,读写网络数据逻辑单元业务进程或线程网络存储单元数据库、…

18.网络爬虫—Scrapy实战演示

网络爬虫—Scrapy实战演示 Scrapy Shell简介进入shell调试网站启动Scrapy Shell 查看目标网站获取网站源代码常用方法调试xpath提取数据Scrapy请求子页面请求及返回处理创建项目创建爬虫 数据解析写入csv文件后记 前言: 🏘️🏘️个人简介&…

Java,jdbc,jvm

1、数据删除 物理删除 直接发送delete语句 就是物理删除 这种删除 删除之后不可恢复逻辑删除 本质是更新 0 表示不可用 1 可用状态 update emp set is_active where id ?2、数据更新 1 显示所有数据 2. 点击修改按钮 此时 应该执行的动作-查询该用户信息 目的是将当前用户…

并发编程的那些事

目录 一、并发编程的目的 二、线程和进程2.1 什么是线程2.2 进程2.3 一个普通Java 程序包含哪些线程 三、并发、并行四、线程的六个状态五、wait 和sleep的区别5.1 位于不同的类5.2 关于锁的释放 一、并发编程的目的 并发编程的目的是为了让程序运行得更快,但是&…

类ChatGPT逐行代码解读(1/2):从零起步实现Transformer、ChatGLM-6B

前言 最近一直在做类ChatGPT项目的部署 微调,关注比较多的是两个:一个LLaMA,一个ChatGLM,会发现有不少模型是基于这两个模型去做微调的,说到微调,那具体怎么微调呢,因此又详细了解了一下微调代…

ggplot中坐标轴和图例的相关处理

文章目录 改变坐标轴和图例的名称方法1, labs()方法2,scale_xxx_discrete/continuous() 删除坐标轴和图例的名称方法1, labs()方法2,scale_xxx_discrete/continuous()方法3,theme()方法4,guides()可以去图例名称 改变图…

osgwidget 使用 方法以及案例分享

osgwidget 使用 方法以及案例分享 按钮 一个常见的 osg::Widget 就是按钮。下面的代码展示了如何使用 osg::Switch 和 osgText 创建一个简单的按钮&#xff1a; osg::ref_ptr<osg::Switch> buttonSwitch new osg::Switch(); osg::ref_ptr<osgText::Text> buttonTe…

[论文阅读] (29)李沐老师视频学习——2.研究的艺术·找问题和明白问题的重要性

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…

写在28岁,回看3年前的自己,庆幸当时入了软件测试这行

为什么会学习软件测试&#xff1f; 已经28岁了&#xff0c;算一下快过去3年了&#xff0c;刚毕业那会工作了一年&#xff0c;因为自己当时很迷茫&#xff08;觉得自己挺废的&#xff09;&#xff0c;所以就没去工作就一直在家&#xff0c;家里固定每个月给点生活费&#xff0c…

人工智能、ChatGPT等火爆的当下 AI大模型爆发

4月18日&#xff0c;火山引擎在其举办的“原动力大会”上发布自研DPU等系列云产品&#xff0c;并推出新版机器学习平台&#xff1a;支持万卡级大模型训练、微秒级延迟网络&#xff0c;让大模型训练更稳更快。火山引擎总裁谭待表示&#xff0c;AI大模型有巨大潜力和创新空间&…

chatgpt实际是怎样工作的?

文章翻译自&#xff1a; https://www.assemblyai.com/blog/how-chatgpt-actually-works/ ChatGPT 是 OpenAI 的最新语言模型&#xff0c;比其前身 GPT-3 有了重大改进。与许多大型语言模型类似&#xff0c;ChatGPT 能够为不同目的生成多种样式的文本&#xff0c;但具有更高的精…

用ChatGPT搭建公司内部ChatGPT服务器

一、前言 我是ChatGPT&#xff0c;一个由OpenAI训练的大型语言模型。我被设计用于回答各种问题并生成文本&#xff0c;可以处理多种自然语言任务&#xff0c;例如问答、摘要和翻译等。在我的学习过程中&#xff0c;我阅读了数百万篇文本&#xff0c;并使用这些文本来提高我的理…