JavaEE之多线程编程:2.创建线程及Thread类常见方法(超全!!!)

news2024/12/26 11:49:24

一、创建线程

Java中创建线程的写法有很多种!!!这里介绍其中5种。

方法1:继承Thread类,重写run

创建一个类,让这个类继承自Thread父类,再重写我们的run方法就可以了。
使用Thread类,不需要import别的包,因为它是再Java.lang下面的。

//写一个类,继承自标准库的Thread
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello word!");
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        //创建线程,是希望线程成为一个独立的执行流(执行一段代码)
        //创建线程是相当于雇了个人帮我们干活
        Thread t = new MyThread(); //这里就不用new标准库的thread的了,而是刚才创建的子类
        t.start(); //线程中的特殊方法,启动一个线程
    }
}

注意:
start() 是创建了一个新的线程,由新的线程来执行t.run()方法。

这个新的线程就是调用操作系统的API
通过操作系统内核创建新线程的PCB,并且把要执行的指令交给这个PCB,当PCB被调度到了CPU上执行的时候,也就执行到了线程run方法的代码了。

如果只是在main方法中输出"hello world",你的Java进程主要就是有一个线程(调用main方法的线程),主线程通过t.start(),主线程调用stat(),创建出一个新的线程,新的线程调用t.run(),如果我们run()方法执行完毕,这个线程自然销毁了。

方法2:实现Runnable接口

//Runnable 作用是描述一个“要执行的任务”,run 方法就是任务的执行细节。
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class ThreadDemo2 {
    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() {
            @Override
            public void run() {
                super.run();
                System.out.println("hello");
            }
        };
        t.start();
    }
}

其中new Thread()
①创建了一个Thread子类,(子类没有名字)所以才叫做“匿名”;
②创建了子类的实例,并且让 t 引用指向该实例。

方法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();
    }
}

这个写法和方法2本质相同,只不过把实现Runable任务交给匿名内部类的语法。
此处是创建了一个类,实现Runable,同时创建了类的实例,并且传给Thread的构造方法。

方法5:使用Lambda表达式

此方法是最简单,最推荐的方法。

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

把任务用lambda表达式来描述,直接把lambda传给Thread构造方法。
lambda就是个匿名函数(没有名字的函数),用一次就没有了。
() - > {}

二、Thread类及常见方法

Thread类是JVM用来管理线程的一个类,换句话说,每个线程都有一个Thread对象与之相关联
可以说,每个执行流,都需要有一个对象来描述,类似下图,而Thread类的对象,就是用来描述一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。
在这里插入图片描述

1. Thread的常见构造方法

在这里插入图片描述
其中:
最后两个Thread(String name)Thread(Runnable target, String name) 多了个name参数,这个参数是为了方便调试而给线程起了个名字。
线程的默认的名字为 thread-0,1,2之类的。

构造方法的格式如下:

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(“这是我的名字”);
Thread t4 = new Thread(new MyRunnable(), “这是我的名字”);

例如:

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

2. Thread的几个常见属性

在这里插入图片描述

  • ID是线程的唯一标识,不同线程不会重复;

  • 名称是各调试工具用到的;

  • getStat:构造方法里起的名字;

  • getStat:线程状态,Java中线程的状态要比操作系统原生的状态更丰富一些;

  • getPrior:线程的优先级可以获取,也可以设置,但是设置了不管用,因为优先级是很多要素影响得到的;

  • isDaem:是否守护线程,是否“后台线程”
    前台线程会阻止进程结束,前台线程的工作没做完,进程是完不了的;
    后台线程,不会阻止线程结束,后台线程没做完,进程是可以结束的。
    代码里手动创建的线程,默认都是前台线程,包括main,默认也是前台;
    其他JVM自带的线程都是后台线程。
    也可以手动使用setDaemon设置成后台线程,是后台线程,就是守护线程。
    在这里插入图片描述
    把t设置成 守护/后台线程,此时进程的结束与否就和t无关了。

  • isAlive()是否存活:判断当前系统中的这个线程是不是有了。
    在用户态(应用程序)中创建了一个线程Thread t = new Thread();
    程序中通过t.start();来调用,
    在真正调用start之前,调用t.isAlive(),此时是false,此时内核态(操作系统内核)里没有这个线程;
    而调用start后,就会让内核创建一个PCB,此时这个PCB才表示一个真正的线程,此时isAlive是 true。
    也可以简单的理解为start/run方法是否允许结束了。

    另外,如果内核里线程把run执行完了,此时线程销毁,PCB随之释放。但是Thread t 这个对象还不一定被释放,此时isAlive也是false。
    【总结】
    如果 t.run还没执行,isAlive 为 false
    如果 t,run 正在执行,isAlive 为 true
    如果 t.run执行结束,isAlive 为 false

    通过上述我们可以了解,Thread t这个对象比内核里的PCB存在的周期要久。

3. 启动一个线程——start()

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

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

总之,run方法是描述了我们要做啥任务,而stat才是真正开始任务。
调用start方法,才真正在操作系统的底层创建出一个线程!

4. 终止一个线程

终止线程的意思是,不是让线程立即就停止,而是通知线程,你应该要停止了,但是是否真的停止,取决于线程这里的具体写法。终止线程有以下两种方式:

  1. 使用标志位来控制线程是否要停止;
public class ThreadDemo8 {
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException{
        Thread t = new Thread(() -> {
            while (flag) { //当flag为true的时候
                System.out.println("hello!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
            t.start();

            Thread.sleep(3000); //当执行到第三秒的时候,把flag变为false,结束循环
            flag = false;
        }
}

在这里插入图片描述
这个代码之所以能够起到作用,修改flag,t线程就结束,完全取决于t线程内部的代码,代码里通过flag控制循环。
因此,这里只是告诉让这个线程结束,这个线程是否要结束,什么时候结束,都是线程内部自己代码来决定的。因为是while(flag)所以3秒钟后结束了,要是while(true),怎么改都不会结束循环。

此方法的缺点:自定义变量这种方式,不能及时相应,尤其在sleep休眠的时间比较久的时候。

  1. 使用Thread自带的标志位( interrupt() 方法),来进行判定是否要停止。

这里调用interrupt,只是通知终止,不是线程一定要乖乖终止!!
【格式】

while (!Thread.currentThread().isInterrupted()) {}

其中:
① while判定,判断条件是true,则执行方法体内的循环,为false,则循环结束;
Thread.currentThread() :这是Thread类的静态方法,通过这个方法可以获取到当前线程。哪个线程调用这个方法,就是得到哪个线程的对象引用(类似于this);
isInterrupted() 为true表示被终止,为false表示未被终止,继续执行;
④ 前面有个 ! 逻辑取反符,所以当isInterrupted() 为true时,!isInterrupted() 就为false,反之,则为true。

interrupt 会做两件事:
① 把线程内部的标志位(boolean)给设置成 true;
② 如果线程在进行sleep,就会触发异常,把sleep给唤醒。

但是sleep在唤醒的时候,还会做一件事,就是把刚在设置的这个标志位,再设置回false。(清空了标志位),这就导致,当sleep的异常被catch完了之后,循环继续执行!
如下述例子:

【例1】:线程t忽视了终止请求

public class ThreadDemo9 {
    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(); //3秒后,把线程内部的标志位(boolean)给设置成 true
    }
}

在这里插入图片描述
此时循环会一直执行下去!这里就是sleep清空的例子。

【例2】:线程t立即响应了终止请求(加了break)

public class ThreadDemo9 {
    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();
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(3000);

        t.interrupt(); //3秒后,把线程内部的标志位(boolean)给设置成 true
    }
}

在这里插入图片描述

【例3】:稍后进行终止
执行完等了3秒钟,代码执行完毕。

public class ThreadDemo9 {
    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();
                    //稍后再终止!
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                }
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(3000);

        t.interrupt();
    }
}

【总结】
大前提:调用interrupt,只是告诉线程你该终止了,但是它是不是真的终止,这是它自己的事情。
注意其中sleep有个清楚标志位的情况,唤醒之后,线程终不终止,立即还是稍后终止,就把选择全交给程序员自己了。

5. 等待一个线程

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作,这是我们需要一个方法明确等待线程的结束
可理解为等待一个线程结束!
线程是一个随机调度的过程,等待线程做的事,就是再控制两个线程结束的顺序。

【例1】

public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("hello!!!");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t.start();
        System.out.println("join 之前");

        //此处的join就是让当前的main线程来等待t线程执行结束(等待t的run执行完)
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("join之后");
    }
}

在这里插入图片描述
执行过程:
t 调用了start()后,t 线程和 main主线程就并发执行,分头行动;
主线程这里打印了“join之前”,同时t线程打印了hello thread;
我们的t线程在执行过程中,主线程并没有打印join之后,而是在join这里等待了一会(发生阻塞block),等待3s之后,t线程执行完了,我们的主线程才会执行后面的 “join之后”;
主线程,等待t线程彻底执行完毕之后,才继续往下执行了。
通过输出,我们也能看出 t 线程肯定比 main 线程先结束。

【例2】

public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("hello!!!");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("join 之前");

        //此处的join就是让当前的main线程来等待t线程执行结束(等待t的run执行完)
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("join之后");
    }
}

在这里插入图片描述
在join前加了一个等待5s钟,此时在执行join的时候,t 已经结束了,所以join不会堵塞,就会立即返回。
在这里插入图片描述

  • public void join() :无参版本,一直等;
  • public void join(long millis) :指定一个超时时间(最大等待时间),这种是最常见的,一直等很容易又问题。

【总结】
线程没结束,就等待,线程结束了,就立即返回;
总之可以保证这两个线程的返回顺序。

6. 获取当前线程引用

public static Thread currentThread();
返回当前线程对象的引用。

调用这个方法,不需要实例,直接通过类名来调用

	Thread t = new Thread();
	Thread.currentThread();

在哪个线程中调用,就能获取到哪个线程的实例

7. 休眠当前线程

是我们比较熟悉的一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。

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

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(System.currentTimeMillis());
        Thread.sleep(3 * 1000);
        System.out.println(System.currentTimeMillis());
   }
}

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

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

相关文章

文件批量管理方法:100个文件要怎样快速放在100个指定的文件夹中

处理大量文件时&#xff0c;经常要将多个文件放入相应的文件夹中。如果要处理的文件数量较大&#xff0c;例如100个文件要放入100个指定的文件夹中&#xff0c;那么如何快速有效地完成这个任务呢&#xff1f;下面看下云炫文件管理批量管理文件的方法&#xff0c;快速将100个文件…

数据结构之----二叉树、二叉树遍历、二叉树数组表示、二叉搜索树

数据结构之----二叉树、二叉树遍历、二叉树数组表示、二叉搜索树 什么是二叉树&#xff1f; 二叉树是一种非线性数据结构&#xff0c;代表着祖先与后代之间的派生关系&#xff0c;体现着“一分为二”的分治逻辑。 与链表类似&#xff0c;二叉树的基本单元是节点&#xff0c;每…

jsonpath:使用Python处理JSON数据

使用Python处理JSON数据 25.1 JSON简介 25.1.1 什么是JSON JSON全称为JavaScript Object Notation&#xff0c;一般翻译为JS标记&#xff0c;是一种轻量级的数据交换格式。是基于ECMAScript的一个子集&#xff0c;采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清…

强化学习--背景

背景 强化学习 背景方向马尔可夫决策过程动态规划 方向 从数据中学习&#xff0c;或者从演示中学习包含丰富的门类&#xff0c;例如以模仿学习为代表的来自专家的数据中学习策略、以强化逆学习&#xff0c;代表来自数据中学习奖励函数以及来自人类反馈中学习&#xff0c;为代表…

大数据机器学习与深度学习—— 生成对抗网络(GAN)

GAN概述 在讲GAN之前&#xff0c;先讲一个小趣事&#xff0c;你知道GAN是怎么被发明的吗&#xff1f;据Ian Goodfellow自己说&#xff1a; 之前他一直在研究生成模型&#xff0c;可能是一时兴起&#xff0c;有一天他在酒吧喝酒时&#xff0c;在酒吧里跟朋友讨论起生成模型。然…

使用 Timm 库替换 YOLOv8 主干网络 | 1000+ 主干融合YOLOv8

文章目录 前言版本差异说明替换方法parse_moedl( ) 方法_predict_once( ) 方法修改 yaml ,加载主干论文引用timm 是一个包含最先进计算机视觉模型、层、工具、优化器、调度器、数据加载器、数据增强和训练/评估脚本的库。 该库内置了 700 多个预训练模型,并且设计灵活易用。…

Python 从入门到精通 学习笔记 Day04

Python 从入门到精通 第四天 今日目标 数据类型-又见str、数据类型-又见list 列表切片&排序&反转&循环、字典 数据类型 - 又见str 字符串定义 字符串是一个有序的字符的集合&#xff0c;用于在计算机里存储和表示文本信息 创建 a "Hello ,my name is Ha…

鸿蒙开发框架(ArkUI)简单解析

方舟开发框架&#xff08;简称ArkUI&#xff09;为HarmonyOS应用的UI开发提供了完整的基础设施&#xff0c;包括简洁的UI语法、丰富的UI功能&#xff08;组件、布局、动画以及交互事件&#xff09;&#xff0c;以及实时界面预览工具等&#xff0c;可以支持开发者进行可视化界面…

Jetson Xavier NX开发环境配置——编译libusb-1.0.9

背景 新买的Jetson Xavier NX 8G微雪的开发板&#xff0c;刷机后虽然已经带了libusb的库&#xff0c;在命令窗口输入lsusb也能够找到usb设备。但是&#xff0c;光机的usb配置说明中提示最好把老版本的libusb卸载掉&#xff0c;安装libusb-1.0.9版本&#xff0c;因此&#xff0…

【语义分割数据集】——imagenet语义分割

地址&#xff1a;https://github.com/LUSSeg/ImageNet-S 1 例图 2. 类别和数量信息 疑问 根据原文的描述&#xff1a;Based on the ImageNet dataset, we propose the ImageNet-S dataset with 1.2 million training images and 50k high-quality semantic segmentation annot…

【Vue第5章】vuex_Vue2

目录 5.1 理解vuex 5.1.1 vuex是什么 5.1.2 什么时候使用vuex 5.1.3 案例 5.1.4 vuex工作原理图 5.2 vuex核心概念和API 5.2.1 state 5.2.2 actions 5.2.3 mutations 5.2.4 getters 5.2.5 modules 5.3 笔记与代码 5.3.1 笔记 5.3.2 23_src_求和案例_纯vue版 5.3…

2023年12月12日 Go生态洞察:探索不可达函数与`deadcode`工具

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

现代雷达车载应用——第2章 汽车雷达系统原理 2.5节 检测基础

经典著作&#xff0c;值得一读&#xff0c;英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.5 检测基础 对于要测试目标是否存在的雷达测量&#xff0c;可以假定下列两个假设之一为真&#xff1a; •H0:—测量结果仅为噪声。 •H1:—测量是噪…

波奇学Linux:环境变量,本地变量和内建命令

Windows下的环境变量 echo $PATH 查看指令搜索命令路径 在bash命令行输入的指令&#xff0c;系统根据PATH中的路径查询。 增加PATH指令 $PATH等于上面的路径 :表示不同路径分割符 /home/boki/lesson13代表新的路径 相当于一个赋值语句。 相当于指令&#xff0c;可以直接使用…

一张图片组合一组动作就可以生成毫无违和感的视频!

你敢信&#xff0c;1张人物图片 1张动作动画&#xff0c;就可以生成一段视频。网友直呼&#xff1a;“主播/视频UP主可能快要下岗了&#xff01;” &#xff08;模型视频来源于网络&#xff09; 本周&#xff0c;字节跳动联合新加坡国立大学发布了一款开源项目 MagicAnimate&…

超声波测距HC-SR04模块的简单应用

文章目录 一、HC-SR04HC-SR04是什么&#xff1f;HC-SR04测距的原理 二、使用步骤1.硬件最远探测距离调节硬件连接 2.软件1.初始化配置代码如下&#xff08;示例&#xff09;&#xff1a;引脚初始化定时器初始化 2.引脚输入输出配置代码如下&#xff08;示例&#xff09;&#x…

verilog基础,连续赋值之组合逻辑

连续赋值语句可以完成任意组合逻辑&#xff0c;本节对基本的逻辑电路进行测试分析&#xff0c;主要包含一下内容&#xff1a; 1. 反相器 2. 与门 3.与非门 4.或门 5.或非门 6.异或门 7.同或门 verilog实现逻辑操作的算符如下 // ~ .... Invert a single-bit signal…

【网络通信原理之套接字】

目录 概念 分类 数据报套接字&#xff1a;使用传输层UDP协议 流套接字&#xff1a;使用传输层TCP协议 原始套接字 Socket编程注意事项 前言&#xff1a;本文主要介绍了在什么是套接字及在Java中套接字是什么&#xff0c;和在套接字编程的注意事项。 概念 Socket套接…

Postman接口测试工具使用

一、前言 在前后端分离开发时&#xff0c;后端工作人员完成系统接口开发后&#xff0c;需要与前端人员对接&#xff0c;测试调试接口&#xff0c;验证接口的正确性可用性。而这要求前端开发进度和后端进度保持基本一致&#xff0c;任何一方的进度跟不上&#xff0c;都无法及…

K8S(四)—pod详解

目录 pod介绍Pod的概念&#xff1a;Pod的特性&#xff1a;Pod的配置&#xff1a;Pod的控制&#xff1a;示例 YAML 文件&#xff1a; pod启动流程问题 两种方式启动镜像的升级和回滚更新 Deployment&#xff1a;回滚检查 Deployment 历史版本回滚到之前的修订版本缩放 Deploymen…