Java多线程:常见的线程的创建方法及Thread类详解


目录

一.并发编程相关概念

线程与进程

多线程

Java中线程的状态

二.线程的创建方法

方法一:继承Thread类

方法二:实现Runnable接口

其他方法

三.Thread类详解

Thread常见构造方法

Thread常见属性

Thread常见方法

start() 与 run() 

sleep() 与 yield() 

join() 

inerrupt() 


一.并发编程相关概念

线程与进程

线程是程序的执行流程的最小单元。一个进程(程序的执行实例)可以由一个或多个线程组成,每个线程都有自己的执行路径和执行状态。线程可以并发执行,即多个线程可以同时在不同的处理器核心或计算机上运行,从而提高程序的运行效率。

线程与进程的区别在于,进程是操作系统对一个正在运行的程序的抽象,而线程是进程内部的一个执行单位。一个进程可以有多个线程,这些线程共享进程的资源,如内存空间、文件描述符等。线程之间可以通过共享内存的方式进行通信,相比于进程间通信(如管道、消息队列)的开销更小。

多线程

对于多线程,我们可以举出这样的一个例子来帮助我们理解

一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴纳社保。 如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找 来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队, 自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。 此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别 排队执行。

 对于这样的业务场景,张三、李四和王五各自都相对于一个线程,多个线程之间相互配合才促使了整体业务流程的顺利进行,由此可见多线程对于任务处理的高效。其中由于李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread),李四和王五则为其他线程。

Java中线程的状态

Java中线程的状态有以下几种:

1. 新建(New):线程被创建但还没有开始执行。

2. 就绪(Runnable):线程被调度并准备开始执行,但还没有获取CPU执行权。

3. 运行(Running):线程正在执行任务。

4. 阻塞(Blocked):当线程执行到某个阻塞操作时,如等待IO操作完成或等待某个锁的释放时,线程会进入阻塞状态。

5. 等待(Waiting):线程执行了Object类的wait()方法,或者Thread类的join()方法时,线程会进入等待状态。

6. 超时等待(Timed Waiting):线程执行了Thread类的sleep()方法或等待超时后,线程会进入超时等待状态。

7. 终止(Terminated):线程执行完任务后或者出现异常终止时,线程进入终止状态。

二.线程的创建方法

线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库)

而Java标准库中 Thread 类,便可以视为是对操作系统提供的 API 进行了进一步的抽象和封装,作为Java程序员就可以利用Thread 类来实现并发编程。

并发编程是指在计算机系统中,多个独立的任务同时进行,每个任务由一个或多个线程执行,并且这些线程可能在同一时刻同时运行。并发编程可以提高系统的执行效率和资源利用率。在并发编程中,多个线程可以同时进行不同的操作,比如读写数据、计算、网络通信等,它们可以同时执行,不需要等待其他线程的完成。常见的并发编程模型有多线程、异步编程、并行计算等。

说了这么多,归根结底还得落实到代码上,我们常见的创建线程的方式有俩种。

方法一:继承Thread类

  1. 创建一个继承自Thread类的子类。
  2. 在子类中重写run()方法,定义线程的执行逻辑。
  3. 在主线程中创建子类对象,并调用start()方法启动线程。
public class MyThread extends Thread {public void run() {// 线程执行逻辑}public static void main(String[] args) {MyThread thread = new MyThread();thread.start();}
}

方法二:实现Runnable接口

  1. 创建一个实现了Runnable接口的类,并实现接口中的run()方法,定义线程的执行逻辑。
  2. 在主线程中创建Runnable实例,并将其作为参数传递给Thread类的构造方法。
  3. 调用Thread对象的start()方法启动线程。
public class MyRunnable implements Runnable {public void run() {// 线程执行逻辑}public static void main(String[] args) {MyRunnable runnable = new MyRunnable();Thread thread = new Thread(runnable);thread.start();}
}

无论是继承Thread类还是实现Runnable接口,都可以创建多个线程并同时运行,以实现并发执行的效果。

其他方法

除此之外,使用匿名内部类或lambda表达式可以更快速的创建线程

匿名内部类创建Thread 子类对象

// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {@Overridepublic void run() {System.out.println("使用匿名类创建 Thread 子类对象");}
};

匿名内部类创建 Runnable 子类对象

// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("使用匿名类创建 Runnable 子类对象");}
});

 lambda 表达式创建Runnable 子类对象

// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {System.out.println("使用匿名类创建 Thread 子类对象");
});

三.Thread类详解

不管是上述创建线程中的哪一种方法,归根结底都是由 Thread 类延申开来的,Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

Thread常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用Runnable对象创建线程对象,并命名
Thread(ThreadGroup group, Runnable target)线程可以被用来分组管理,分好的组即为线程组

Thread常见属性

属性获取方法

ID

getId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

其中 ID 是线程的唯一标识,不同线程不会重复,优先级高的线程理论上来说更容易被调度到,是否存活,简单的理解的话就是 run 方法是否运行结束了

Thread常见方法

start() 与 run() 

  • start()方法是Thread类中的一个方法,用于启动一个新的线程。当调用start()方法时,系统会创建一个新的线程,并在新的线程中执行run()方法的内容。start()方法会在新的线程中执行一些准备工作,然后调用run()方法。
  • run()方法是实现了Runnable接口的类中的一个方法。在启动一个线程后,系统会自动调用该线程对象的run()方法。run()方法中包含了线程的主体代码,即线程要执行的任务。

前文中我们已经了解了如何通过重写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。重写 run 方法是提供给线程要做的事情的指令清单,线程对象可以认为是把李四、王五叫过来了,而调用start() 方法,就是喊一声:“行动起来!”,线程才真正独立去执行。

public class MyThread extends Thread {public void run() {// 线程执行逻辑}public static void main(String[] args) {MyThread thread = new MyThread();thread.start();}
}

就拿上面这段代码来说,我们能不能不管start方法直接调用线程的run方法呢?

其实是可以调用run方法的,但是直接调用run方法只会在当前线程中运行run方法的代码,不会启动新的线程去执行run方法。

而调用start方法则会启动一个新的线程,然后在新的线程中执行run方法的代码。所以,如果只调用run方法而不调用start方法,则不会创建新的线程,无法实现多线程的并发执行。

也就是说,我们可以这样做,但是这样做的话就不会实现多线程,始终我们都只能在一个线程中运行。因此在实际开发中,并不建议这样做。 

还有一点需要注意的是,对于start方法,我们只能调用一次,不能重复调用,不然会报非法线程状态异常,因为在调用start方法后,线程就已经处于Runnable状态,对于已经是Runnable状态的线程,再让它start为Runnable状态显然是不合理的。

sleep() 与 yield() 

在多线程编程中,可以使用sleep和yield方法来控制线程的执行。

  • sleep方法:sleep方法是Thread类提供的静态方法,可以使当前线程暂停一段时间,让其他线程有机会执行。调用sleep方法后,线程会进入阻塞状态,不会占用CPU资源。sleep方法的语法是:Thread.sleep(long millis),其中millis参数表示暂停的时间,以毫秒为单位。例如,Thread.sleep(100)表示暂停100毫秒。
  • yield方法:yield方法是Thread类提供的静态方法,可以使当前线程让出CPU资源,使其他同优先级的线程有机会执行。调用yield方法后,线程会进入就绪状态,让出CPU资源,但并不是完全放弃CPU资源,可能会立即重新获取CPU资源。yield方法的语法是:Thread.yield()。例如,Thread.yield()表示当前线程让出CPU资源,给其他同优先级的线程执行的机会。

总的来说,sleep方法是让当前线程暂停一段时间,不会占用CPU资源,适合用于控制线程执行的时间间隔。yield方法是让当前线程主动让出CPU资源,给其他同优先级的线程执行的机会,适合用于在多个线程之间平衡负载,提高系统的性能。

join() 

join()方法是Thread类的一个方法,它用于等待该线程完成执行。具体而言,当调用一个线程的join()方法时,当前线程会被阻塞,直到该线程执行完成。

join()方法有两个重载版本:

  1. join():等待被调用线程执行完成。
  2. join(long millis):等待被调用线程执行完成,但最多等待millis毫秒。

下面是一个例子,演示如何使用join()方法等待线程执行完成:

public class JoinExample {public static void main(String[] args) {Thread thread1 = new Thread(new MyRunnable(), "Thread 1");Thread thread2 = new Thread(new MyRunnable(), "Thread 2");thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("All threads have finished execution.");}
}class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " is executing.");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " has finished execution.");}
}

在上面的例子中,我们创建了两个线程thread1和thread2,并将它们启动。然后,我们使用join()方法等待这两个线程执行完成。最后,当两个线程都执行完成后,才会打印"All threads have finished execution."。

inerrupt() 

在Java中,线程的interrupt()方法用于中断线程。当一个线程调用interrupt()方法时,如果目标线程当前正在执行可中断的操作(如sleep()、join()、wait()等),它将会收到一个InterruptedException异常,从而提前退出。

如果目标线程没有在可中断操作中阻塞,而是在运行中,那么调用interrupt()方法将设置目标线程的中断标志位为true。这样,目标线程可以通过检查自己的中断标志位来自行决定是否中断执行。

下面是一个示例:

public class MyThread extends Thread {public void run() {while (!Thread.currentThread().isInterrupted()) {// 执行一些操作}System.out.println("线程被中断");}public static void main(String[] args) {MyThread thread = new MyThread();thread.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt();}
}

在上述示例中,我们创建了一个继承自Thread类的自定义线程MyThread。在run() 方法中,我们使用了一个循环来模拟线程的执行操作。每次循环都会检查中断标志位,如果为true则退出循环并输出"线程被中断"。在前文的Thread类常见属性也说过了使用 isInterrupted() 方法就可以获取当前线程的中断标志位。

备注:和 isInterrupted() 相似的还有一个方法叫做 interrupt() 二者都能判断线程是否被打断,但是不同的点在于前者只是做出判断,并不会手动修改这个标记;而后者会在判断后手动清除打断标记,也就是置为false。

在main方法中,我们创建了一个MyThread对象并启动线程。然后通过调用Thread.sleep() 方法来让主线程睡眠1秒,最后调用thread.interrupt() 方法中断线程。当调用interrupt() 方法时,MyThread线程在下一个循环迭代时会检查到中断标志位为true,从而退出了循环并输出"线程被中断"。




 本次的分享就到此为止了,希望我的分享能给您带来帮助,创作不易也欢迎大家三连支持,你们的点赞就是博主更新最大的动力!如有不同意见,欢迎评论区积极讨论交流,让我们一起学习进步!有相关问题也可以私信博主,评论区和私信都会认真查看的,我们下次再见

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

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

相关文章

Redis教程——主从复制

在上篇文章我们学习了Redis教程——管道,这篇文章学习Redis教程——主从复制。 主从复制 为了数据更加安全可靠,在实际的项目中,肯定是有多个Redis服务,主机Redis以写为主,从机Redis以读为主,当主机Redis…

长难句打卡5.7

In December 2010 America’s Federal Trade Commission (FTC) proposed adding a “do not track” (DNT) option to Internet browsers, so that users could tell advertisers that they did not want to be followed. 2010年12月,美国美国联邦贸易委员会(FTC)提…

在数字化转型的浪潮中,CBDB百数服务商如何破浪前行?

在信息化时代,传统咨询企业面临着数字化转型的挑战与机遇。如何利用数字化技术提升业务效率、增强客户黏性,成为了行业关注的焦点。云南析比迪彼企业管理有限公司(CBDB)作为云南地区的企业咨询服务提供商,率先与百数展…

Springai入门

一、概述 1.1发展历史 1.2大模型 大模型,是指具有大规模参数和复杂计算结构的机器学习模型。这些模型通常由深度神经网络构建而成,拥有数十亿甚至数千亿个参数。其设计目的在于提高模型的表达能力和预测性能,以应对更加复杂的任务和数据&…

虚幻引擎5 Gameplay框架(二)

Gameplay重要类及重要功能使用方法(一) 配置LOG类及PlayerController的网络机制 探索验证GamePlay重要函数、类的执行顺序与含义 我们定义自己的日志,专门建立一个存放自己日志的类,这个类继承自BlueprintFunctionLibrary 然后…

零基础怎么学鸿蒙开发?

对于零基础的学习者来说,掌握鸿蒙开发不仅是迈向新技术的第一步,更是开拓职业道路的重要机遇。随着鸿蒙系统在各行各业的应用逐渐扩展,对于掌握这一项技术的开发人员需求也随之增长。下文将为大家提供针对零基础学习鸿蒙开发的逻辑&#xff0…

提高曝光率与点击率:海外KOL推广与社交媒体算法的完美结合

在当今数字化时代,社交媒体已经成为了企业和品牌推广的重要渠道之一,海外KOL也在各个领域拥有着庞大的粉丝群体和影响力。然而,想要在众多内容中脱颖而出,提高推广内容的曝光率和点击率,仅仅依靠KOL的影响力是不够的&a…

论文阅读-THE GENERALIZATION GAP IN OFFLINE REINFORCEMENT LEARNING(ICLR 2024)

1.Motivation 本文希望比较online RL、offline RL、序列决策和BC等方法的泛化能力(对于不同的初始状态、transition functions、reward functions,现阶段offline RL训练的方式都是在同一个环境下的数据集进行训练)。实验发现offline的算法相较于online算法对新环境…

特别提醒:2024年软考连考时间再次调整!

近期,辽宁省发布了今年5月软考考试安排,有以下变化: 高级论文单独考试(2小时),之前是案例和论文一起考,案例剩下的时间可以给论文用,现在是综合和案例一起考,综合剩下的…

菜鸡学习netty源码(一)——ServerBootStrap启动

1.概述 对于初学者而然,写一个netty本地进行测试的Server端和Client端,我们最先接触到的类就是ServerBootstrap和Bootstrap。这两个类都有一个公共的父类就是AbstractBootstrap. 那既然 ServerBootstrap和Bootstrap都有一个公共的分类,那就证明它们两个肯定有很多公共的职…

Redis集群.md

Redis集群 本章是基于 CentOS7 下的 Redis 集群教程,包括: 单机安装RedisRedis主从Redis分片集群 1.单机安装Redis 首先需要安装Redis所需要的依赖: yum install -y gcc tcl然后将课前资料提供的Redis安装包上传到虚拟机的任意目录&#xf…

如何在CentOS部署青龙面板并实现无公网IP远程访问本地面板

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Java双亲委派机制

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 概述 Java程序在运…

一起长锈:4 默认不可变的变量绑定与引用(从Java与C++转Rust之旅)

讲动人的故事,写懂人的代码 故事梗概:在她所维护的老旧Java系统即将被淘汰的危机边缘,这位在编程中总想快速完事的女程序员,希望能转岗到公司内部使用Rust语言的新项目组,因此开始自学Rust;然而,在掌握了Rust编程知识之后,为了通过Rust项目组的技术面试,使得转岗成功而…

sql优化思路

sql的优化经验 这里解释一下SQL语句的优化的原理 1.指明字段名称,可以尽量使用覆盖索引,避免回表查询,因此可以提高效率 2.字面意思,无需过多赘述。索引就是为了提高查询效率的。 3.图中两条sql直接可以使用union all 或者 uni…

羊大师分解,羊奶不为人知的健康奇迹

羊大师分解,羊奶不为人知的健康奇迹 羊奶是一种营养丰富的食品,拥有许多不为人知的健康益处。 益智健脑:羊奶中含有较高的钙、蛋白质、氨基酸、维生素、矿物质、无机盐、核酸等营养成分,这些都对大脑发育及增强智力有很大帮助。…

MySQL mydumper工具

目录 1. mydumper介绍 2. mydumper参数解释 3. 备份例子 3.1 备份全库(未包含其他) 3.2 备份全库(包含其他) 3.3 备份指定数据库(-B或--database) 3.4 导出指定表(-T或--tables-list) 3.5 只导出表结构,不导出表数据(-d或--no-data) 3.6 只导出表数据&#…

包管理工具npm的安装和使用

包管理工具 管理 包 的应用软件,可以对 包 进行下载 安装,更新,删除,上传 等操作。 借助包管理工具,可以快速开发项目,提升开发效率。 包管理工具是一个通用的概念,很多编程语言都有包管理工…

likeshop多商户单商户商城_likeshop跑腿源码_likeshop物品租赁系统开源版怎么配置小程序对接?

本人是商业用户所以能持续得到最新商业版,今天我说下likeshop里面怎么打包小程序,大家得到程序时候会发现它有admin目录 app目录 server目录 这三个目录分别是做什么呢? 1.admin目录 下面都是架构文件使用得是Node.js打包得,至于…

管道通信与Linux命令的执行-(读书笔记-十三)

|前一个命令的输出作为后一个命令的输入。 在Linux中,|符号是一个管道符号,用于将前一个命令的输出作为后一个命令的输入。这种机制允许你将多个命令组合在一起,以执行复杂的操作。下面是一些基本的例子: 查看当前目录下的文件列…