Java多线程--创建多线程的基本方式一:继承Thread类

文章目录

  • 一、创建和启动线程
    • (1)概述
    • (2)方式1:继承Thread类
      • 1、使用步骤
      • 2、举例
        • 2.1 案例1
        • 2.2 案例2
        • 2.3 案例3
      • 3、两个问题
        • 3.1 问题1
        • 3.2 问题2
      • 4、代码及总结
  • 二、练习
    • (1)方式一
    • (2)方式二

一、创建和启动线程

(1)概述

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

image.png

(2)方式1:继承Thread类

1、使用步骤

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

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

如下:

image.png

举例代码如下:

package com.atguigu.thread;
//自定义线程类
public class MyThread extends Thread {//定义指定线程名称的构造方法public MyThread(String name) {//调用父类的String参数的构造方法,指定线程的名称super(name);}/*** 重写run方法,完成该线程执行的逻辑*/@Overridepublic 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) {//创建自定义线程对象1MyThread mt1 = new MyThread("子线程1");//开启子线程1mt1.start();//创建自定义线程对象2MyThread mt2 = new MyThread("子线程2");//开启子线程2mt2.start();//在主方法中执行for循环for (int i = 0; i < 10; i++) {System.out.println("main线程!"+i);}}
}

image.png

注意:

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

2、举例

2.1 案例1

🌋描述:创建一个分线程1,用于遍历100以内的偶数。

🚗分析

<1> 创建一个继承于Thread类的子类

public class EvenNumberTest {}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{}

<2> 重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中

public class EvenNumberTest {}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if(i%2==0){System.out.println(i);}}}
}

<3> 创建当前Thread的子类的对象

public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1=new PrintNumber();}
}

<4>通过对象调用start(): 1.启动线程 2.调用当前线程的run()

public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1=new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if(i%2==0){System.out.println(i);}}}
}

当前PrintNumber类里面没有重写start方法,这就意味着调用的是父类Thread里面的start方法。

这个start()方法有什么作用呢?

image.png

所以start方法有两个作用:1.启动线程 2.调用当前线程的run()方法。

在刚才的代码中,t1调用start()方法,这里的start()方法是父类中的

此时调用了当前线程的run()方法,这个方法在Thread类里面定义的,并且在子类PrintNumber里面被重写了,所以父类中的方法被覆盖了,此时调用的就是子类的run()方法

整体来看,调用t1.start(),子类中的run()方法就被执行了。


🌱整体代码

package yuyi01.thread;/*** ClassName: EvenNumberTest* Package: yuyi01.thread* Description:*      创建一个分线程1,用于遍历100以内的偶数* @Author 雨翼轻尘* @Create 2024/1/19 0019 11:57*/
public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1=new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if(i%2==0){System.out.println(i);}}}
}

🍺输出结果(部分)

image.png

2.2 案例2

刚才并没有感觉到线程的存在啊,现在修改一下代码:

package yuyi01.thread;public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1=new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();//main()方法所在的线程执行的操作:for (int i = 1; i <= 100; i++) {if(i%2==0){System.out.println(i+"main()做的事情");}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if(i%2==0){System.out.println(i);}}}
}

此时有两个线程:

image.png

再次执行代码:(部分)

image.png

此时它们没有交互,如果数字足够多,会有交叉的数据出现,就是交替执行

这说明两个线程都在执行,前面的执行一下,后面的执行一下。谁先执行都有可能。这里就体现出了两个不同的线程

2.3 案例3

其实这里还有一种方式可以看出来是两个线程在交替执行,需要用到一个方法,后面再说,这里先用一下。

Thread有个方法叫做currentThread()获取当前执行的线程;然后它又有一个getName()方法,即获取线程的名字。

即:

Thread.currentThread().getName()

🌱代码

package yuyi01.thread;/*** ClassName: EvenNumberTest* Package: yuyi01.thread* Description:*      创建一个分线程1,用于遍历100以内的偶数* @Author 雨翼轻尘* @Create 2024/1/19 0019 11:57*/
public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1=new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();//main()方法所在的线程执行的操作:for (int i = 1; i <= 10000; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName() +":" +i+"**********");}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {for (int i = 1; i <= 10000; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName() +":" +i);}}}
}

🍺输出结果(部分)

image.png

可以看到,现在的执行结果,前面都有自己线程的名字。

t1线程也有自己的线程名字,默认叫Thread-0。因为此时new的是当前类PrintNumber的对象,默认的是调用super(),是父类Thread里面的构造器,如下:

image.png

所以,当我们不给线程起名字的时候,默认第一个线程就是Thread-0,第二个线程就是Thread-1

注意下面:

image.png

3、两个问题

3.1 问题1

☕问题1

启动线程,包括调用上面的run方法都用的是start()

那么能否使用t1.run()来替换t1.start()的调用,实现分线程的创建和调用?

🍰分析

比如现在这样写:

image.png

运行看一下:

image.png

可以看到不仅没有交替执行,而且还都是main,没有Thread-0了。它认为run()方法是主线程main做的。

此时就是主线程造了一个t1对象,然后就调用了run()这个普通的方法,执行完之后再执行循环输出。只有一条线程了。这就不是多线程问题了。


再举个例子,看看下面的代码是不是多线程:

public class SingleThread {public void method1(String str) {System.out.println(str);}public void method2(String str) {method1(str);}public static void main(String[] args) { //main线程SingleThread s = new SingleThread();s.method2("hello!");}}

首先main方法进去,造了一个当前类的一个对象s,这个对象s调用method2(),method2()里面调用method1()。这里是单线程
判断单线程还是多线程,就看能不能拿一条线穿起来。

比如此时就可以拿一条线穿起来,只有一条执行路径,那就是一个线程,即单线程。如下:

image.png

之前的那个例子,也是类似,就是一个线程。如下:(用鼠标手画的有点不堪入目哈哈)

image.png

start()不一样,一个作用是启动线程,然后是调用run方法

若是仅仅调用run方法的话,线程并没有启动,相当于还是只有主线程。

所以,不能使用t1.run()来替换t1.start()的调用。

3.2 问题2

☕问题2

比如现在启动了一个线程,调用一下start(),打印了1000以内的偶数。代码如下:

package yuyi01.thread;public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1=new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();//main()方法所在的线程执行的操作:for (int i = 1; i <= 1000; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName() +":" +i+"**********");}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {for (int i = 1; i <= 1000; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName() +":" +i);}}}
}

现在需要再创建一个分线程,同样要去遍历一下1000以内的偶数

那么此时要怎样去做呢?

🍰分析

直接用t1再次调用start()方法可以吗?如下:

image.png

运行结果:

image.png

可以看到,出现了IllegalThreadStateException的异常。

当我们首次调用start()方法的时候,threadStatus的值是0 ,就没有抛异常。当我们再次调用start()之后,threadStatus状态就不是0了,便会抛异常。

image.png

所以,线程不能start()多次。

不能让已经start()的线程,再次执行start()操作,否则报IllegalThreadStateException非法线程状态的异常。


🎲解决方案

既然上面的方法不行,那么究竟应该怎么做呢?

需要重新创建一个对象,类PrintNumber不需要重新创建了,因为要做的事情一样,都是遍历1000以内的偶数。

所以现在只需要再建立一个对象,然后用这个新的对象去调用start()方法即可。

如下:

image.png

🌱整体代码

package yuyi01.thread;public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1=new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();/** 问题2:再提供一个分线程,用于100以内偶数的遍历** 注意:不能让已经start()的线程,再次执行start()操作,否则报IllegalThreadStateException非法线程状态的异常。* *///t1.start();PrintNumber t2=new PrintNumber();t2.start();//main()方法所在的线程执行的操作:for (int i = 1; i <= 1000; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName() +":" +i+"**********");}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {for (int i = 1; i <= 1000; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName() +":" +i);}}}
}

🍺输出结果(部分)

image.png

输出默认的线程名也很好理解,造的第二个对象就是Thread-1

从上面的输出结果可以看到,三个线程交替执行。

4、代码及总结

线程的创建方式一:继承Thread类

步骤:

① 创建一个继承于Thread类的子类

② 重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中

③ 创建当前Thread的子类的对象

④ 通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法

start()是父类Thread的方法。

🌱整体代码总结

package yuyi01.thread;/*** ClassName: EvenNumberTest* Package: yuyi01.thread* Description:*      创建一个分线程1,用于遍历100以内的偶数* @Author 雨翼轻尘* @Create 2024/1/19 0019 11:57*/
public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1=new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();/*问题1:能否使用t1.run()来替换t1.start()的调用,实现分线程的创建和调用?    不能*///t1.run();/** 问题2:再提供一个分线程,用于100以内偶数的遍历** 注意:不能让已经start()的线程,再次执行start()操作,否则报IllegalThreadStateException非法线程状态的异常。* *///t1.start();PrintNumber t2=new PrintNumber();t2.start();//main()方法所在的线程执行的操作:for (int i = 1; i <= 1000; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName() +":" +i+"**********");}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {for (int i = 1; i <= 1000; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName() +":" +i);}}}
}

🍺输出结果(部分)

image.png

二、练习

🌋题目描述

练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数。

(1)方式一

🍰分析

【回顾】

线程的创建方式一:继承Thread类

步骤:

① 创建一个继承于Thread类的子类

② 重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中

③ 创建当前Thread的子类的对象

④ 通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法

【分析】

现在是两个线程,做的事情不一样。run方法里面执行的方法体就不一样了。

两个分线程,这里就不要使用主线程了。

既然两个线程指定的事情不一样,那就写两个run,这就意味着要声明两个类了(要是两个线程做的事情一样,那就只用写一个类,就像上面的案例)。

🌱代码(方式一)

package yuyi01.thread.exer1;/*** ClassName: PrintNumberTest* Package: yuyi01.thread.exer1* Description:** @Author 雨翼轻尘* @Create 2024/1/19 0019 22:38*/
public class PrintNumberTest {public static void main(String[] args) {//方式一EvenNumberPrint t1=new EvenNumberPrint();OddNumberPrint t2=new OddNumberPrint();t1.start();t2.start();}
}//打印偶数
class EvenNumberPrint extends Thread {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + ":"+i);}}}
}//打印奇数
class OddNumberPrint extends Thread {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName() + "-->"+i);}}}
}

🍺输出结果(部分)

image.png

(2)方式二

🍰分析

上面的方式一是一种标准写法,之前还讲过匿名子类的方式,需要提供Thread子类的对象。

所以直接new一个Thread(),因为是匿名子类,后面直接加一对大括号,这样对象就造好了,如下:

new Thread(){}

这时可以声明为t1,然后通过t1去调start(),如下:

Thread t1=new Thread(){};
t1.start();

当然,我们可以不声明它为t1,直接在后面.start(),如下:

new Thread(){}.start();

在大括号里面,可以将run()方法重写一下。如下:

//方式二
new Thread(){//打印偶数public void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + ":"+i);}}}
}.start();new Thread(){//打印奇数public void run() {for (int i = 1; i <= 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName() + "-->"+i);}}}
}.start();

方法二可以叫做,创建Thread类的匿名子类的匿名对象,造完对象之后还顺便将start()方法给调用了。

🌱代码(方式二)

package yuyi01.thread.exer1;/*** ClassName: PrintNumberTest* Package: yuyi01.thread.exer1* Description:** @Author 雨翼轻尘* @Create 2024/1/19 0019 22:38*/
public class PrintNumberTest {public static void main(String[] args) {//方式二new Thread(){//打印偶数public void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + ":"+i);}}}}.start();new Thread(){//打印奇数public void run() {for (int i = 1; i <= 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName() + "-->"+i);}}}}.start();}
}

🍺输出结果(部分)

image.png

可以看到,方法一与方法二的效果一样。

若是以后,有一个临时需求,造一个线程做个事情,可能都不会按照方式一那么正规地写,都是采用匿名的方式做一下。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS鸿蒙学习基础篇 - 项目目录和文件介绍

├── hvigor //存储购置信息的文件&#xff0c;主要用于发布打包 ├── idea //开发工具相关配置可忽略 ├── AppScope //工程目录 全局公共资源存放路径 │ └── resources │ │ └── base │ │ │ └── element //常亮存放 │ │ │ …

AI大模型开发架构设计(2)——AI绘画技术架构应用实践

文章目录 1 AI绘画整体流程2 AI绘画技术架构文生图核心算法原理文生图工程架构 3 AI绘画的应用实践 1 AI绘画整体流程 第一步&#xff1a;输入 Prompt 提示词&#xff1a;/mj 提示词第二步&#xff1a;文生图(Text-to-Image)构图第三步&#xff1a;图片渲染第四步&#xff1a;…

数据结构——Java实现栈和队列

一、栈 Stack 1.特点 &#xff08;1&#xff09;栈是一种线性数据结构 &#xff08;2&#xff09;规定只能从栈顶添加元素&#xff0c;从栈顶取出元素 &#xff08;3&#xff09;是一种先进后出的数据结构&#xff08;Last First Out&#xff09;LIFO 2.具体实现 Java中可…

STM32--7针0.96寸OLED屏幕显示(4线SPI)

本文介绍基于STM32F103C8T60.96寸OLED&#xff08;7针&#xff09;的显示&#xff08;完整程序代码见文末链接&#xff09; 一、简介 OLED&#xff0c;即有机发光二极管&#xff08; Organic Light Emitting Diode&#xff09;。 OLED 由于同时具备自发光&#xff0c;不需背光…

一个关于自动化工具的操作手册

整体 整个软件由首页、设计页构成 首页 按钮功能 清除缓存&#xff1a;主要为了避免线程占用的问题&#xff0c;端口占用无法重新执行。所以操作执行任务、修改任务、新建任务没有响应时&#xff0c;可以操作该事件新建任务&#xff1a;顾名思义就是创建一个网页自动化任务执…

傲空间私有部署Windows指南

推荐阅读 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;一&#xff09; 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;二&#xff09; 安装 docker 请下载对应的 Docker&#xff0c;安装完成后启动。 Docker Desktop for Windows…

(2023版)斯坦福CS231n学习笔记:DL与CV教程 (14) | 强化学习(Robot Learning)

前言 &#x1f4da; 笔记专栏&#xff1a;斯坦福CS231N&#xff1a;面向视觉识别的卷积神经网络&#xff08;23&#xff09;&#x1f517; 课程链接&#xff1a;https://www.bilibili.com/video/BV1xV411R7i5&#x1f4bb; CS231n: 深度学习计算机视觉&#xff08;2017&#xf…

Unity中URP下获取主灯信息

文章目录 前言一、计算BulinnPhone的函数有两个重载1、 目前最新使用的是该方法&#xff08;这是我们之后主要分析的函数&#xff09;2、 被淘汰的老方法&#xff0c;需要传入一堆数据 二、GetMainLight1、Light结构体2、GetMainLight具有4个方法重载3、1号重载干了什么&#x…

漫漫数学之旅008

文章目录 经典格言数学习题古今评注名人小传&#xff08;一&#xff09;莫扎特&#xff08;二&#xff09;赫拉克利特 经典格言 如果我们不期望着意外&#xff0c;那么我们永远找不到意外。——赫拉克利特&#xff08;Heraclitus&#xff09; 赫拉克利特的这句名言“如果我们不…

vbscript和asp.net的一些整理

1、前言 因为工作中有涉及&#xff0c;因此就把一些常用的代码整理了一下。 2、vbscript 2.1、do while循环 Dim rs do while not rs.Eof rs.eof表示结果集无法获取更多的数据即&#xff08;End Of File&#xff09;&#xff0c;表示结果集遍历结束sManagerIDssManagerIDs&a…

【Qt】ubuntu环境下使用命令行安装Qt

起因是我上一篇文章说的&#xff0c;官网下的安装包卡死在第一步安装界面了。 于是我就问GPT有没有纯命令行的安装方式&#xff0c;果然是有的。 在Ubuntu上安装Qt可以使用以下命令&#xff1a; 1. 首先&#xff0c;添加Qt的官方存储库到系统中&#xff1a; sudo add-apt-rep…

线性规划案例分享

今天想写一个最优传输的简单实现&#xff0c;结果学歪了&#xff0c;学到线性规划去了&#xff0c;这里我发现了一个宝藏网站 虽然是讲计量经济的&#xff0c;但是里面提供的公式和代码我很喜欢&#xff0c;有时间可以好好读一下 https://python.quantecon.org/lp_intro.html …