Java 高级应用-多线程-实现 Runnable 接口与继承 Thread 类

news2024/11/24 6:27:23

1.1 程序、进程与线程
• 程序(program):为完成特定任务,用某种语言编写的一组指令的集合。即指一段
静态的代码,静态对象。
• 进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。如:运行
中的 QQ,运行中的网易音乐播放器。
– 每个进程都有一个独立的内存空间,系统运行一个程序即是一个进程从创
建、运行到消亡的过程。(生命周期)
– 程序是静态的,进程是动态的
– 进程作为操作系统调度和分配资源的最小单位(亦是系统运行程序的基
本单位),系统在运行时会为每个进程分配不同的内存区域。
– 现代的操作系统,大都是支持多进程的,支持同时运行多个程序。比如:
现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,
dos 窗口等软件。
• 线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径。
一 个进程中至少有一个线程。
– 一个进程同一时间若并行执行多个线程,就是支持多线程的。
在这里插入图片描述

– 线程作为 CPU 调度和执行的最小单位。
– 一个进程中的多个线程共享相同的内存单元,它们从同一个堆中分配对
象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但
多个线程操作共享的系统资源可能就会带来安全的隐患。
– 下图中,红框的蓝色区域为线程独享,黄色区域为线程共享。

在这里插入图片描述

注意:
不同的进程之间是不共享内存的。
进程之间的数据交换和通信的成本很高。

1.5.2 并行与并发
• 并行(parallel):指两个或多个事件在同一时刻发生(同时发生)。
指在同一时刻,有多条指令在多个 CPU 上同时执行。比如:多个人同时做不同的事。
在这里插入图片描述

**• 并发(concurrency):指两个或多个事件在同一个时间段内发生。**即在一段时间内,有多条指令在单个 CPU 上快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果。
在这里插入图片描述

2.创建和启动线程
2.1 概述
• Java 语言的 JVM 允许程序运行多个线程,使用 java.lang.Thread 类代表线程,所
有的线程对象都必须是 Thread 类或其子类的实例。
• Thread 类的特性
– 每个线程都是通过某个特定 Thread 对象的 run()方法来完成操作的,因此
把 run()方法体称为线程执行体。
– 通过该 Thread 对象的 start()方法来启动这个线程,而非直接调用 run()
– 要想实现多线程,必须在主线程中创建新的线程对象。

2.2 方式 1:继承 Thread 类

Java 通过继承 Thread 类来创建并启动多线程的步骤如下:

  1. 定义 Thread 类的子类,并重写该类的 run()方法,该 run()方法的方法体就代表了线程
    需要完成的任务
  2. 创建 Thread 子类的实例,即创建了线程对象
  3. 调用线程对象的 start()方法来启动该线程
    代码如下:
package com.atguigu.thread;
//自定义线程类
public class MyThread extends Thread {
 //定义指定线程名称的构造方法
 public MyThread(String name) {
 //调用父类的 String 参数的构造方法,指定线程的名称
 super(name);
 }
 /**
 * 重写 run 方法,完成该线程执行的逻辑
 */
 @Override
 public void run() {
 for (int i = 0; i < 10; i++) {
 System.out.println(getName()+":正在执行!"+i);
 }
 }
}

测试类:


package com.atguigu.thread;
public class TestMyThread {
 public static void main(String[] args) {
 //创建自定义线程对象 1
 MyThread mt1 = new MyThread("子线程 1");
 //开启子线程 1
 mt1.start();
 
 //创建自定义线程对象 2
 MyThread mt2 = new MyThread("子线程 2");
 //开启子线程 2
 mt2.start();
 
 //在主方法中执行 for 循环
 for (int i = 0; i < 10; i++) {
 System.out.println("main 线程!"+i);
 }
 }
}

在这里插入图片描述
注意:

1.如果自己手动调用 run()方法,那么就只是普通方法,没有启动多线程模 式。
2.run()方法由 JVM 调用,什么时候调用,执行的过程控制都有操作系统的 CPU 调度决定。
3.想要启动多线程,必须调用 start 方法。
4.一个线程对象只能调用一次 start()方法启动,如果重复调用了,则将抛出 以上的异常“IllegalThreadStateException”。

2.3 方式 2:实现 Runnable 接口

Java 有单继承的限制,当我们无法继承 Thread 类时,那么该如何做呢?在核心
类库中提供了 Runnable 接口,我们可以实现 Runnable 接口,重写 run()方法,
然后再通过 Thread 类的对象代理启动和执行我们的线程体 run()方法

步骤如下:
2. 定义 Runnable 接口的实现类,并重写该接口的 run()方法,该 run()方法的方法体同样是该线程的线程执行体。
3. 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 参数来创建Thread 对象,该 Thread 对象才是真正 的线程对象。
4. 调用线程对象的 start()方法,启动线程。调用 Runnable 接口实现类的run 方法。
代码如下:

package com.atguigu.thread;
public class MyRunnable implements Runnable {
 @Override
 public void run() {
 for (int i = 0; i < 20; i++) {
 System.out.println(Thread.currentThread().getName() + " "
+ i);
 }
 }
}

测试类:

package com.atguigu.thread;
public class TestMyRunnable {
 public static void main(String[] args) {
 //创建自定义类对象 线程任务对象
 MyRunnable mr = new MyRunnable();
 //创建线程对象
 Thread t = new Thread(mr, "长江");
 t.start();
 for (int i = 0; i < 20; i++) {
 System.out.println("黄河 " + i);
 }
 }
}

通过实现 Runnable 接口,使得该类有了多线程类的特征。所有的分线程要执行的代码都在 run 方法里面。
在启动的多线程的时候,需要先通过 Thread 类的构造方法 Thread(Runnable target) 构造出对象,然后调用 Thread 对象的 start()方法来运行多线程代码。
实际上,所有的多线程代码都是通过运行 Thread 的 start()方法来运行的。因此,不管是继承 Thread 类还是实现 Runnable 接口来实现多线程,最终还是通过 Thread 的对象的 API 来控制线程的,熟悉 Thread 类的 API 是进行多线程编程的基础。

说明:Runnable 对象仅仅作为 Thread 对象的 target,Runnable 实现类里包含的 run()方法仅作为线程执行体。
而实际的线程对象依然是 Thread 实例,只是该 Thread 线程负责执行其 target 的 run()方法。

2.5 对比两种方式

联系

Thread 类实际上也是实现了 Runnable 接口的类。即: public class Thread extends Object implements Runnable

区别

• 继承 Thread:线程代码存放 Thread 子类 run 方法中。
• 实现 Runnable:线程代码存在接口的子类的 run 方法。

实现 Runnable 接口比继承 Thread 类所具有的优势

• 避免了单继承的局限性
• 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资 源。
•增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

3. Thread 类的常用结构
3.1 构造器

public Thread() :分配一个新的线程对象。
• public Thread(String name) :分配一个指定名字的新的线程对象。
• public Thread(Runnable target) :指定创建线程的目标对象,它实现了 

Runnable 接口中的 run 方法

public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

3.2 常用方法系列 1

public void run() :此线程要执行的任务在此处定义代码。
• public void start() :导致此线程开始执行; Java 虚拟机调用此线程的 run 方法。
• public String getName() :获取当前线程名称。
• public void setName(String name):设置该线程名称。
• public static Thread currentThread() :返回对当前正在执行的线程对象的引用。在
Thread 子类中就是 this,通常用于主线程和 Runnable 实现类
• public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时
停止执行)。
• public static void yield()yield 只是让当前线程暂停一下,让系统的线程调度器重新
调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这
个不能保证,完全有可能的情况是,当某个线程调用了 yield 方法暂停之后,线程调
度器又将其调度出来重新执行。

4.1 JDK1.5 之前:5 种状态
线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行
(Running)、阻塞(Blocked)、死亡(Dead)。
CPU 需要在多条线程之间切
换,于是线程状态会多次在运行、阻塞、就绪之间切换

在这里插入图片描述
1.新建
当一个 Thread 类或其子类的对象被声明并创建时,新生的线程对象处于新建状
态。此时它和其他 Java 对象一样,仅仅由 JVM 为其分配了内存,并初始化了
实例变量的值。此时的线程对象并没有任何线程的动态特征,程序也不会执行
它的线程体 run()。
2.就绪
但是当线程对象调用了 start()方法之后,就不一样了,线程就从新建状态转为
就绪状态。JVM 会为其创建方法调用栈和程序计数器,当然,处于这个状态中
的线程并没有开始运行,只是表示已具备了运行的条件,随时可以被调度。至
于什么时候被调度,取决于 JVM 里线程调度器的调度。
注意:
程序只能对新建状态的线程调用 start(),并且只能调用一次,如果对
非新建状态的线程,如已启动的线程或已死亡的线程调用 start()都会
报错 IllegalThreadStateException 异常。
3.运行
如果处于就绪状态的线程获得了 CPU 资源时,开始执行 run()方法的线程体代
码,则该线程处于运行状态。如果计算机只有一个 CPU 核心,在任何时刻只有
一个线程处于运行状态,如果计算机有多个核心,将会有多个线程并行
(Parallel)执行。
当然,美好的时光总是短暂的,而且 CPU 讲究雨露均沾。对于抢占式策略的系
统而言,系统会给每个可执行的线程一个小时间段来处理任务,当该时间用
完,系统会剥夺该线程所占用的资源,让其回到就绪状态等待下一次被调度。
此时其他线程将获得执行机会,而在选择下一个线程时,系统会适当考虑线程
的优先级。
4.阻塞
当在运行过程中的线程遇到如下情况时,会让出 CPU 并临时中止自己的执
行,进入阻塞状态:
• 线程调用了 sleep()方法,主动放弃所占用的 CPU 资源;
• 线程试图获取一个同步监视器,但该同步监视器正被其他线程持有;
• 线程执行过程中,同步监视器调用了 wait(),让它等待某个通知(notify);
• 线程执行过程中,同步监视器调用了 wait(time)
• 线程执行过程中,遇到了其他线程对象的加塞(join);
• 线程被调用 suspend 方法被挂起(已过时,因为容易发生死锁);
当前正在执行的线程被阻塞后,其他线程就有机会执行了。针对如上情况,当
发生如下情况时会解除阻塞,让该线程重新进入就绪状态,等待线程调度器再
次调度它:
• 线程的 sleep()时间到;
• 线程成功获得了同步监视器;
• 线程等到了通知(notify);
• 线程 wait 的时间到了
• 加塞的线程结束了;
• 被挂起的线程又被调用了 resume 恢复方法(已过时,因为容易发生死锁);
5.死亡
线程会以以下三种方式之一结束,结束后的线程就处于死亡状态:
• run()方法执行完成,线程正常结束
• 线程执行过程中抛出了一个未捕获的异常(Exception)或错误(Error)
• 直接调用该线程的 stop()来结束该线程(已过时)

4.2 JDK1.5 及之后:6 种状态
在 java.lang.Thread.State 的枚举类中这样定义:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
• NEW(新建):线程刚被创建,但是并未启动。还没调用 start 方法。
• RUNNABLE(可运行):这里没有区分就绪和运行状态。因为对于 Java 对象来说,只
能标记为可运行,至于什么时候运行,不是 JVM 来控制的了,是 OS 来进行调度的,
而且时间非常短暂,因此对于 Java 对象的状态来说,无法区分。
• Teminated(被终止):表明此线程已经结束生命周期,终止运行。
• 重点说明,根据 Thread.State 的定义,阻塞状态分为三种:BLOCKED、WAITING、
TIMED_WAITING。
– BLOCKED(锁阻塞):在 API 中的介绍为:一个正在阻塞、等待一个监视
器锁(锁对象)的线程处于这一状态。只有获得锁对象的线程才能有执行
机会。
• 比如,线程 A 与线程 B 代码中使用同一锁,如果线程 A 获取到
锁,线程 A 进入到 Runnable 状态,那么线程 B 就进入到 Blocked
锁阻塞状态。
– TIMED_WAITING(计时等待):在 API 中的介绍为:一个正在限时等待
另一个线程执行一个(唤醒)动作的线程处于这一状态。
• 当前线程执行过程中遇到 Thread 类的 sleep 或 join,Object 类
的 wait,LockSupport 类的 park 方法,并且在调用这些方法时,
设置了时间,那么当前线程会进入 TIMED_WAITING,直到时间
到,或被中断。
– WAITING(无限等待):在 API 中介绍为:一个正在无限期等待另一个线
程执行一个特别的(唤醒)动作的线程处于这一状态。
• 当前线程执行过程中遇到遇到 Object 类的 wait,Thread 类的
join,LockSupport 类的 park 方法,并且在调用这些方法时,没
有指定时间,那么当前线程会进入 WAITING 状态,直到被唤醒。
– 通过 Object 类的 wait 进入 WAITING 状态的要有 Object 的
notify/notifyAll 唤醒;
– 通过 Condition 的 await 进入 WAITING 状态的要有
Condition 的 signal 方法唤醒;
– 通过 LockSupport 类的 park 方法进入 WAITING 状态的要有
LockSupport 类的 unpark 方法唤醒
– 通过 Thread 类的 join 进入 WAITING 状态,只有调用 join
方法的线程对象结束才能让当前线程恢复;
说明:当从 WAITING 或 TIMED_WAITING 恢复到 Runnable 状态时,如果发现
当前线程没有得到监视器锁,那么会立刻转入 BLOCKED 状态。

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

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

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

相关文章

转变范式:如何使用 5 种新模式重塑 2023 年的实体店体验

在电商盛行的当下&#xff0c;线上购物已成为新零售的重要组成部分&#xff0c;实体零售业正处于两难境地。一方面&#xff0c;实体零售是绝对有必要的&#xff1a;美国约 85% 的销售额来自实体商店。 另一方面&#xff0c;尽管增长放缓&#xff0c;但电商收入占销售总额的比例…

chatgpt赋能python:Python中图形怎么整体下移?

Python中图形怎么整体下移&#xff1f; 在Python中&#xff0c;我们常常需要处理各种各样的图形&#xff0c;但是有时候我们需要将图形进行整体调整&#xff0c;比如将所有图形下移一定距离。那么在Python中&#xff0c;我们该如何实现这个操作呢&#xff1f; 介绍 在Python…

【Linux】3、iptables

文章目录 一、设置其他机器均无法访问 a、b、c 机器的 5432 端口二、设置 d、e 可访问 a、b、c 的 5432 端口三、检查业务是否受到影响 iptables 可在 tcp 协议栈层面限制访问&#xff0c;常用于解决现场的各漏洞。 场景&#xff1a;现场有 a、b、c 三个机器组成的 postgres 集…

经纬恒润AUTOSAR成功适配智芯科技国产车规级芯片

近日&#xff0c;经纬恒润AUTOSAR基础软件产品INTEWORK-EAS-CP成功适配智芯半导体的Z20K14x产品家族。同时&#xff0c;经纬恒润完成了对智芯半导体Z20K14X 产品MCAL软件适配和工程集成&#xff0c;为智芯半导体提供了全套AUTOSAR解决方案。 左图&#xff1a;经纬恒润AUTOSAR E…

iOS 创建组件库

索引库 repo 索引文件(.podspec文件) 1.搭建私有库之前必须要先创建索引库 1&#xff09;首先检查当前电脑的索引库 pod repo 2&#xff09;在gitlab上创建一个新的库&#xff0c;这个库用来保存私有库的podspec文件&#xff0c;所以我们一般起名字最好是 xxxSpec用以区分…

回归预测 | MATLAB实现SSA-CNN-BiLSTM麻雀算法优化卷积双向长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现SSA-CNN-BiLSTM麻雀算法优化卷积双向长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现SSA-CNN-BiLSTM麻雀算法优化卷积双向长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计学习总结参考资料 预测效果 基本介…

在虚拟机上部署hadoop集群(全流程)

一、单节点部署前置准备 这里一直下一步就好了 接下来需要为该节点固定ip并配置相关网关还有dns解析 这里配置了一个本机dns解析,也配置了一个公网dns解析(主要目的还是为了固定ip

知识变现:知识付费产品,怎么定价?

知识变现&#xff1a;知识付费产品&#xff0c;怎么定价&#xff1f; 定价&#xff0c;是知识博主或者培训机构、用户、竞争对手间博弈平衡后的结果。 200元以下: 只有产品&#xff0c;没有服务。 产品的特征是标准化、大批量。这样的知识产品&#xff0c;要卖给几千、几万…

CSS 实现一个动态水形波浪蒙版层

需求的最终实现效果&#xff0c;如下图&#xff0c;在盒子表面&#xff0c;绘制一个波浪形状的蒙版层&#xff08;动态的&#xff09;。 先定义一个&#xff0c;主体盒子块&#xff0c;等会儿的蒙版图层会覆盖到它的上面。 <div></div>图层采用&#xff0c;SVG进…

Spring Boot 如何自定义异常处理器

Spring Boot自定义异常处理器 在Spring Boot应用程序中&#xff0c;异常处理是一个非常重要的方面。如果您不处理异常&#xff0c;应用程序可能会崩溃或出现不可预料的行为。默认情况下&#xff0c;Spring Boot将未捕获的异常返回给客户端。这通常不是期望的行为&#xff0c;因…

【Java基础】注解与反射

一、学习笔记 &#xff08;本文内容基本源自参考链接1视频教程&#xff09; 1、注解的含义 1&#xff09;注解&#xff08;annotation)是从jdk5.0开始引入的新技术&#xff0c;其作用&#xff1a;不是程序本身&#xff0c;可对程序作解释&#xff08;该作用与注释comment相同…

iOS 性能优化方案-弱网优化

一、iPhone手机弱网环境配置 选择现有网络状态 或自定义网络状态 设置参数: 每个参数的含义大致如下: in bandwidth &#xff1a;下行带宽 in packet loss &#xff1a;下行丢包率 in delay &#xff1a;下行延迟(ms) out bandwidth &#xff1a;上行带宽 out packet los…

【解决】升级g++到8版本

升级g到8版本 g不提高到最新版本在一些操作会头文件报错&#xff0c;因此下面我们配置g编译器 要升级g编译器到版本8&#xff0c;您可以尝试以下步骤&#xff1a; 添加Developer Toolset存储库&#xff1a;在CentOS上&#xff0c;可以使用Red Hat Developer Toolset存储库来获…

PMP课堂模拟题目及解析(第15期)

141. 在新项目的干系人会议中&#xff0c;项目经理发现一名干系人对项目有抵触。项目经理记录这个问题&#xff0c;并对该干系人的参与程度评级。项目经理使用了哪项工具或技术来为干系人的参与程度评级&#xff1f; A. 干系人参与评估矩阵 B. 风险概率和影响评估 C. 人际关…

文件夹显示无法访问、拒绝访问需要权限的解决方法

为了简便管理资料&#xff0c;我们都会选择在电脑上建立文件夹来保存不同作用的资料。文件夹显示无法访问、拒绝访问需要权限的解决方法但是当我们遇到某些文件夹打不开无法访问、拒绝访问时该怎么办呢&#xff1f;这里和大家讲一个方法来解决遇到文件夹无法访问、拒绝访问的这…

Seata1.6.1的安装部署

一、业务系统&#xff08;一个简单采购系统&#xff0c;模拟采购同时增加库存&#xff09; 项目地址&#xff1a;JAVA学习代码: java学习代码&#xff0c;包括一些练习用的开源项目 二、部署TC服务 1、部署Seata的tc-server 1.1、下载 下载地址&#xff1a;下载中心 (seata.…

Rust语言从入门到入坑——(1)初窥Rust,这是个什么鬼!

文章目录 0 引入1、特性2、应用3、总结 0 引入 最近新闻来说&#xff0c;windows部分核心使用Rust语言重写&#xff0c;linux支持rust语言&#xff0c;我就在想什么魔力让该语言如此受大家喜爱&#xff0c;晚上搜了一下&#xff0c;对该语言最大的感触就是&#xff1a;入门难&…

APACHE-ATLAS-2.1.0 - ATLAS的RESTAPI?(四)

查看接口 1. 官方DOC&#xff1a;https://atlas.apache.org/api/v2/index.html 2. 官方SWAGGER&#xff1a;https://atlas.apache.org/api/v2/ui/index.html#/ 接口说明 &#xff08;1&#xff09;管理接口 ①. 查看服务器的状态 http://192.168.64.174:21000/api/atla…

什么是一致性哈希?一致性哈希是如何工作的?如何设计一致性哈希?

1.什么是一致性哈希&#xff1f;一致性哈希是如何工作的&#xff1f;如何设计一致性哈希&#xff1f;05-25 2.系统设计&#xff1a;从零用户扩展到百万用户05-28 收起 如果你有 n 个缓存服务器&#xff0c;一个常见的负载均衡方式是使用以下的哈希方法&#xff1a; 服务器索…

UE5的IK Rig重定向注意问题

推荐先把官方文档看仔细&#xff0c;明白IK Rig重定向原理&#xff0c;对理解UE5怎么进行动画骨骼重定向&#xff0c;以及重定向后骨骼出现问题应该怎么调整非常有帮助。 IK Rig重定向 主要功能点 IK Rig IK Rig的作用是定义两个转化关系中的源骨骼和目标骨骼的主要部件的骨…