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

news/2024/11/16 18:36:59/


目录

一.并发编程相关概念

线程与进程

多线程

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()方法启动线程。
java">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()方法启动线程。
java">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 子类对象

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

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

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

 lambda 表达式创建Runnable 子类对象

java">// 使用 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() 方法,就是喊一声:“行动起来!”,线程才真正独立去执行。

java">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()方法等待线程执行完成:

java">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。这样,目标线程可以通过检查自己的中断标志位来自行决定是否中断执行。

下面是一个示例:

java">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.ppmy.cn/news/1456077.html

相关文章

C语言中的关键字static和extern

Hello,亲爱的小伙伴们,我又来了,上一期作者菌讲解了C语言中函数的知识点,得到了很好的反馈,这里作者菌感谢每一个至此我的小伙伴!!今天作者菌又来补充一些很有用的知识,感兴趣的uu们不要吝啬手中…

【Qt】按钮类控件

文章目录 1 :peach:Push Button:peach:2 :peach:Radio Buttion:peach:3 :peach:Check Box:peach:4 :peach:Tool Button:peach: 1 🍑Push Button🍑 使⽤ QPushButton 表⽰⼀个按钮,这也是当前我们最熟悉的⼀个控件了,QPushButton …

为什么 ChatGPT 不火了?

不火了是有原因的,下面我来从大部分人拿到 ChatGPT 之后的两大痛点开始讲起: 很多朋友拿到 ChatGPT 后的第一个痛点就是:用的不好 你经常会感觉到 ChatGPT 回答的好空,没有太多参考价值。 而第二个痛点则是:无处去用…

前端技术Stylus详解与引入

Stylus 是一种动态样式语言,它允许使用更少的代码来生成 CSS。它是一个预处理器,这意味着你需要在浏览器加载前将 Stylus 代码转换成 CSS。Stylus 提供了多种功能,如变量、混合(mixins)、函数、继承等,这些…

【强训笔记】day10

NO.1 思路:中心扩展。从i位置开始,从i-1为左边和i1右边进行移动,字符相等就继续移动,直到不等,更新回文串长度,让i为左边,i1右边再移动,同样字符相等就移动,不等就更新长…

组件通信-(父子组件通信)

目录 一、什么是组件通信 二、组件关系的分类 三、组件通信解决方案 四、父传子 五、子传父 一、什么是组件通信 组件通信,就是指组件与组件之间的数据传递。组件的数据是独立的,无法直接访问其他组件的数据。如果想使用其他组件的数据,…

鸿蒙内核源码分析(事件控制篇) | 任务间多对多的同步方案

官方概述 先看官方对事件的描述. 事件(Event)是一种任务间通信的机制,可用于任务间的同步。 多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。 一对多同步模型…

#04 构建您的第一个神经网络:PyTorch入门指南

文章目录 前言理论基础神经网络层的组成前向传播与反向传播 神经网络设计步骤1:准备数据集步骤2:构建模型步骤3:定义损失函数和优化器步骤4:训练模型步骤5:评估模型结论 前言 在过去的几天里,我们深入了解了…