前言:在Java中,Thread
类是实现多线程编程的核心。它允许程序同时执行多个任务,提高应用程序的响应能力和性能。通过Thread
类,开发者可以轻松创建和管理线程,并实现复杂的并发操作。接下来,我们将探讨Thread
类的基本用法及其在实际开发中的应用。
✨✨✨这里是秋刀鱼不做梦的BLOG
✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客
在开始讲解并查集之前,先让我们看一下本文大致的讲解内容:
目录
1.Java中线程的回顾
2.Thread线程的五种创建方式
(1)继承 Thread 类创建线程
(2)实现 Runnable 接口创建线程
(3)使用匿名内部类创建 Thread 子类对象
(4)使用匿名内部类创建 Runnable 子类对象
(5)使用 Lambda 表达式创建线程
3.多线程的状态与调度
(1)线程状态
(2)线程调度
4.Thread类的属性及基本方法的使用
【1】Thread类的基本属性
(1)线程 ID(ID)
(2)线程名称(Name)
(3)线程状态(State)
(4)线程优先级(Priority)
(5)后台线程(Daemon)
(6)线程存活状态(Alive)
(7)线程中断标志(Interrupted)
【2】Thread类的常用方法
(1)启动一个线程 - start()
(2)中断一个线程 - interrupt()
(3)等待一个线程 - join()
(4)获取当前线程引用 - Thread.currentThread()
(5)休眠当前线程 - Thread.sleep()
1.Java中线程的回顾
线程是程序中执行代码的最小单位,通常被称为执行流。每个线程都有自己独立的运行路径,并且可以与其他线程并行执行代码。多个线程共享同一进程的资源,比如内存空间和文件句柄,但它们之间可以独立调度。
——这就像在现实生活中,我们有多个员工(线程)在公司(进程)里同时工作,虽然他们共享办公空间和资源,但每个人都有各自的任务要完成。
在程序中,线程能够提高并发性,处理多任务,减少等待时间。线程的概念可以通过如下的代码理解:
public class ThreadDemo {
private static class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在运行");
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start(); // 启动第一个线程
t2.start(); // 启动第二个线程
}
}
在这个例子中,MyThread
类继承了 Thread
,我们通过 start()
方法启动了两个线程 t1
和 t2
,它们分别执行各自的任务。线程可以独立运行,即使它们共享相同的进程资源。
——所以我们为什么需要线程来帮助我们工作呢?
多线程编程的需求主要来自两个方面:提高多核CPU的利用率 和 处理I/O等待问题。
提高多核CPU的利用率 随着硬件的发展,CPU的单核性能提升遇到了瓶颈,现代的计算机通常采用多核CPU。为了充分利用这些多核资源,程序需要能够并行处理多个任务,这时多线程编程就成为了一个关键手段。例如,如果一个程序能够同时在多个CPU核心上运行不同的线程,整体计算效率就会成倍提升。
解决I/O等待问题 有时,程序需要等待某些耗时的I/O操作(如文件读写或网络请求)完成,而在等待这些操作时,CPU的资源可能会被浪费掉。通过引入多线程,程序可以在等待I/O时继续执行其他任务,从而提高整体效率。就像在餐馆里,厨师可以同时准备多道菜,而不是等待每道菜完成后再开始下一个。
这里我们在使用一个日常生活中的例子来进行理解:
假设你在银行办理业务,需要分别处理财务转账、员工福利发放、社保缴纳。如果只有一个员工来处理所有这些事务,他需要排队等候多个业务完成,这会拖慢整个流程。而如果每个业务都有专门的员工负责,它们可以同时进行,显著提高效率。这就是多线程在程序中的表现。
——通过上述的回顾,我相信读者已经可以回想起有关Java中线程的内容了!!!
2.Thread线程的五种创建方式
回顾完有关Java中线程的知识之后,让我们正式的开始本篇文章的主要内容,那么一开始,让我们先看一下在Java中如何去创建一个线程,其有以下五种创建方式:
(1)继承 Thread
类创建线程
这是最直接的方式,通过继承 Thread
类并重写其 run()
方法来定义线程的执行逻辑。Thread
类本身实现了 Runnable
接口,因此每个 Thread
对象都可以被当作一个线程。通过调用 start()
方法,线程会开始运行,操作系统的线程调度器会选择适当的时机执行 run()
方法中的代码。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread1 = new MyThread(); // 创建线程对象
thread1.start(); // 启动线程
}
}
该种方式创建线程的优点:
-
简单直观,适合需要对线程本身进行自定义控制的场景
该种方式创建线程的缺点:
-
Java 不支持多继承,如果线程类已经继承了其他类,就不能再继承
Thread
类,因此这种方式具有局限性。
(2)实现 Runnable
接口创建线程
相比继承 Thread
类,实现 Runnable
接口 是更灵活的方式,因为它解耦了线程的任务与线程对象本身。通过实现 Runnable
接口并将其传递给 Thread
对象,我们可以实现类似的功能。这种方式尤其适合在需要继承其他类时使用。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行");
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable); // 创建线程对象并传入 Runnable 对象
thread.start(); // 启动线程
}
}
该种方式创建线程的优点:
-
线程任务与线程对象分离,符合面向接口编程的原则。
-
适用于需要继承其他类的场景,更加灵活。
该种方式创建线程的缺点:
-
与直接继承
Thread
相比,代码稍显复杂,需要额外的Runnable
对象。
(3)使用匿名内部类创建 Thread
子类对象
为了简化代码,Java 提供了匿名内部类的方式来创建线程对象。这种方式通过定义一个匿名子类并重写 run()
方法,可以在一行代码中同时创建线程并指定其任务。匿名内部类适合用于短小的、一次性的线程任务。
public class AnonymousThreadExample {
public static void main(String[] args) {
// 使用匿名类创建 Thread 子类对象
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("匿名类线程 " + Thread.currentThread().getName() + " 正在执行");
}
};
thread.start(); // 启动线程
}
}
该种方式创建线程的优点:
-
代码简洁,适合一次性任务,避免创建额外的类文件。
该种方式创建线程的缺点:
-
可读性较差,特别是对于复杂任务时,不易维护。
-
由于是匿名类,不能复用或扩展其功能。
(4)使用匿名内部类创建 Runnable
子类对象
类似于匿名 Thread
子类的方式,匿名内部类 也可以用于创建 Runnable
对象,然后将其传递给 Thread
。这种方式依然能够通过实现 Runnable
接口来定义线程任务,并保持代码简洁
public class AnonymousRunnableExample {
public static void main(String[] args) {
// 使用匿名类创建 Runnable 子类对象
Runnable myRunnable = new Runnable() {
@Override
public void run() {
System.out.println("匿名类 Runnable 线程 " + Thread.currentThread().getName() + " 正在执行");
}
};
Thread thread = new Thread(myRunnable); // 创建并启动线程
thread.start();
}
}
该种方式创建线程的优点:
-
灵活且代码简洁,能够避免创建多余的类文件。
-
线程任务与
Thread
对象分离,保持了良好的代码结构。
该种方式创建线程的缺点:
-
可读性较差,对于复杂任务的实现不够清晰。
-
匿名内部类不能被复用。
(5)使用 Lambda 表达式创建线程
在 Java 8 之后,Lambda 表达式大大简化了代码的书写。当任务逻辑较为简单时,可以用 Lambda 表达式代替匿名内部类来实现 Runnable
接口。Lambda 表达式使代码更简洁、易读,同时避免了不必要的语法冗余。
public class LambdaThreadExample {
public static void main(String[] args) {
// 使用 Lambda 表达式创建 Runnable 对象
Thread thread = new Thread(() -> {
System.out.println("Lambda 线程 " + Thread.currentThread().getName() + " 正在执行");
});
thread.start(); // 启动线程
}
}
该种方式创建线程的优点:
-
代码极其简洁,尤其适合简单的线程任务。
-
提升代码的可读性,减少样板代码的编写。
该种方式创建线程的缺点:
-
仅适用于 Java 8 及以上版本。
-
不适合需要大量逻辑或复杂任务的场景。
——以上就是Java中创建线程的五种方式了!!!
3.多线程的状态与调度
了解了Java中的线程如何去创建之后,现在让我们深入Java中的Thread类,看看其状态和其如何去调度的。
(1)线程状态
了解完了如何在Java中去创建一个线程之后,在让我们看一下Java中的线程的状态以及调度,在Java中,线程的生命周期可以分为五种状态:
NEW(新建状态):线程对象被创建,但尚未调用
start()
方法,此时线程还未开始运行。RUNNABLE(可运行状态):线程已经调用
start()
方法,进入就绪队列,等待CPU的调度,或者正在运行中。BLOCKED(阻塞状态):线程等待获取锁时处于阻塞状态。比如当线程试图进入一个
synchronized
块但锁被其他线程占用时,它就会进入BLOCKED
状态。WAITING(等待状态):线程主动等待某个条件的触发,比如调用了
wait()
方法,等待其他线程调用notify()
。TERMINATED(终止状态):线程执行完毕或被异常终止,此时线程已经结束运行。
这里我们使用一段代码来对线程的状态进行控制并打印其状态:
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 1000000; i++) { /* 模拟任务执行 */ }
});
System.out.println("线程状态:" + thread.getState()); // NEW
thread.start();
while (thread.isAlive()) {
System.out.println("线程状态:" + thread.getState()); // RUNNABLE
}
System.out.println("线程状态:" + thread.getState()); // TERMINATED
}
}
在这个代码中,线程 thread
在不同时刻有不同的状态:NEW
表示线程刚刚创建,RUNNABLE
表示线程正在运行,TERMINATED
表示线程已经执行完毕(读者可能不太理解其中的方法,不管美食,读者只要根据该代码对上述的线程状态进行理解即可)。
(2)线程调度
线程的调度由操作系统的线程调度器管理,Java中的线程调度是抢占式的,这意味着线程的执行顺序由系统决定,而不是由程序直接控制。操作系统会根据线程的优先级、系统负载等因素来决定哪个线程获得CPU的时间片。
因此,线程的执行顺序是不确定的,这也是为什么在多线程编程中会出现一些竞争问题,后面我们将进一步讨论。
这样我们就了解了有关多线程的状态与调度了。
4.Thread类的属性及基本方法的使用
通过上述的讲解,我们相信读者已经对Java中的线程有了初步的理解了,那么现在开始,我们就开始学习Java中Thread类的属性和操作Java中线程的方法了。
【1】Thread类的基本属性
在Java中 Thread
类有一些重要的属性,它们用于表示和控制线程的状态和行为,理解这些属性有助于更好地调试和优化多线程程序,以下是 Thread
类中的几个常见属性:
(1)线程 ID(ID)
每个线程都有一个唯一的ID,表示线程的标识符。这个ID是系统自动分配的,且不同线程的ID永远不会重复。线程ID用于唯一标识每个线程,无论线程是否运行,ID都可以帮助我们区分不同的线程。开发者可以通过 getId()
方法获取线程的ID:
long id = thread.getId();
System.out.println("线程 ID: " + id);
线程ID主要用于低级别的系统操作或调试,它是线程的唯一标识符,不能被更改。
(2)线程名称(Name)
线程名称是开发者可以手动指定的属性。通过设置有意义的名称,开发者可以在调试或日志中快速定位某个线程的执行情况。线程的名称可以在创建时指定,也可以在运行过程中通过 setName()
方法更改:
thread.setName("WorkerThread");
String name = thread.getName();
System.out.println("线程名称: " + name);
线程名称不影响线程的执行逻辑,但它对开发者了解线程的功能和状态起到重要作用,特别是在多线程调试中非常有用。
(3)线程状态(State)
线程状态表示线程当前的执行状态,Thread.State
是一个枚举类型,包含了线程的几种典型状态,如 NEW
(新建)、RUNNABLE
(可运行)、BLOCKED
(阻塞)、WAITING
(等待)、TIMED_WAITING
(计时等待)和 TERMINATED
(终止)。开发者可以通过 getState()
方法获取线程的当前状态:
Thread.State state = thread.getState();
System.out.println("线程状态: " + state);
线程状态的变化能够帮助开发者理解线程的生命周期和调度行为,尤其是在遇到阻塞或死锁问题时,状态信息能够提供有力的调试依据。
(4)线程优先级(Priority)
每个线程都有一个优先级,范围从 1 到 10。线程优先级是操作系统用来调度线程的一个参考值,优先级高的线程更有可能获得 CPU 时间片。开发者可以通过 setPriority()
和 getPriority()
方法来设置和获取线程的优先级:
thread.setPriority(Thread.MAX_PRIORITY);
int priority = thread.getPriority();
System.out.println("线程优先级: " + priority);
尽管优先级会影响线程的调度顺序,但并不能保证线程优先级较高的线程一定会首先执行,因为线程调度最终由操作系统控制。
(5)后台线程(Daemon)
后台线程,也称为守护线程,是一种特殊的线程类型。当所有非后台线程结束时,JVM 会自动退出,即使后台线程仍然在运行。典型的后台线程包括垃圾回收线程,它们在系统后台执行一些不需要用户直接干预的操作。可以通过 setDaemon(true)
来将一个线程设置为后台线程:
thread.setDaemon(true);
boolean isDaemon = thread.isDaemon();
System.out.println("是否为后台线程: " + isDaemon);
后台线程非常适合用于执行长期运行且不依赖于用户输入的任务,例如监控、清理任务等。但需要注意的是,JVM 退出时后台线程会被强制停止,可能导致任务未能完全执行完毕。
(6)线程存活状态(Alive)
一个线程在调用 start()
方法之后即被视为“存活”,直到它的 run()
方法执行完毕或者被强制中断后才会结束。开发者可以通过 isAlive()
方法检查线程是否仍在运行:
boolean isAlive = thread.isAlive();
System.out.println("线程是否存活: " + isAlive);
线程的存活状态能够帮助开发者判断一个线程是否已经结束执行,这在需要等待线程完成任务时非常有用。
(7)线程中断标志(Interrupted)
当一个线程被其他线程调用 interrupt()
方法时,它会被标记为中断状态。线程可以通过 isInterrupted()
或 interrupted()
方法来检查自身是否被中断:
thread.interrupt(); // 中断线程
boolean isInterrupted = thread.isInterrupted();
System.out.println("线程是否被中断: " + isInterrupted);
中断通常用于停止线程的执行,特别是在需要安全终止线程时,开发者可以通过捕获 InterruptedException
来终止阻塞中的线程操作(例如 sleep()
)。
以上就是Java中线程的一些基本属性了!!!
【2】Thread类的常用方法
(1)启动一个线程 - start()
在 Java 中,创建了一个 Thread
对象并不意味着线程会立即执行。线程的创建仅仅是分配了一些系统资源,但要让线程真正开始执行,还需要调用 start()
方法。当 start()
方法被调用时,线程进入到 可运行状态(RUNNABLE
),然后等待被系统的线程调度器选中并执行其 run()
方法。
注意: 直接调用
run()
方法不会启动新线程,它只是将run()
方法当作普通的方法来执行,运行在当前调用线程中。真正启动线程的方式是调用start()
方法,它会在 JVM 中真正创建一个新线程。
以下为一个代码案例:
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 线程正在执行");
}
}
public class ThreadStartExample {
public static void main(String[] args) {
MyThread thread = new MyThread(); // 创建线程
System.out.println("线程状态:" + thread.getState()); // NEW
thread.start(); // 启动线程
System.out.println("线程状态:" + thread.getState()); // RUNNABLE
}
}
在上面的代码中,线程在 start()
调用后,从 新建状态(NEW
)切换为 可运行状态(RUNNABLE
),此时线程可以被操作系统调度并执行。
(2)中断一个线程 - interrupt()
在多线程编程中,有时需要安全地停止一个正在执行的线程。Java 提供了 interrupt()
方法,用于通知线程停止执行。需要注意的是,interrupt()
并不会强制终止线程,而是通过设置线程的中断状态来通知线程自己是否应该终止。
线程可以通过检查其中断状态来决定是否结束运行。如果线程正在执行阻塞操作(如 sleep()
或 wait()
),那么 interrupt()
会导致这些操作抛出 InterruptedException
,从而跳出阻塞状态并执行相应的终止逻辑。
以下为一个代码案例:
public class ThreadInterruptExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " 正在执行");
Thread.sleep(1000); // 模拟工作
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 被中断");
}
});
thread.start();
Thread.sleep(3000); // 主线程等待3秒
thread.interrupt(); // 中断线程
}
}
thread.interrupt()
会中断线程,使得 sleep()
操作抛出 InterruptedException
,从而线程终止。
(3)等待一个线程 - join()
有时候,在主线程或其他线程中,我们希望等待某个线程执行完毕再继续执行后续操作。此时,可以使用 join()
方法。join()
会让调用线程进入 等待状态,直到目标线程执行完毕或达到指定的等待时间(如果提供了超时参数)。通过 join()
方法,我们可以确保一个线程在另一个线程执行完成之后才继续执行。
以下为一个代码案例:
public class ThreadJoinExample {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " 正在工作");
try {
Thread.sleep(1000); // 模拟工作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 工作完成");
});
Thread thread2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 准备开始工作");
});
thread1.start();
thread1.join(); // 主线程等待 thread1 完成
thread2.start(); // 只有 thread1 完成后,thread2 才会启动
}
}
在这个例子中,主线程会调用 thread1.join()
来等待 thread1
完成,只有当 thread1
执行完毕后,thread2
才会开始执行。
(4)获取当前线程引用 - Thread.currentThread()
在多线程编程中,有时我们需要获取当前正在执行的线程对象。Java 提供了静态方法 Thread.currentThread()
,它返回对当前线程的引用。通过这个方法,开发者可以获取当前线程的名称、ID、优先级等属性,或对当前线程进行操作(例如检查中断状态)。
public class CurrentThreadExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
// 获取当前线程引用
Thread currentThread = Thread.currentThread();
System.out.println("当前线程名称: " + currentThread.getName());
});
thread.start();
// 获取主线程的引用
Thread mainThread = Thread.currentThread();
System.out.println("主线程名称: " + mainThread.getName());
}
}
在这个示例中,我们使用了 Thread.currentThread()
获取当前线程的引用,并打印了线程的名称。无论是在新启动的线程中还是主线程中,这个方法都可以用于获取当前执行线程的相关信息。
(5)休眠当前线程 - Thread.sleep()
在多线程编程中,常常需要让线程在某些时刻暂停执行一段时间。Java 提供了 Thread.sleep()
方法,可以让线程进入 休眠状态。线程在休眠期间不会占用 CPU 资源,但它仍然保持着某些锁定状态。当休眠时间结束后,线程会恢复到 就绪状态,等待 CPU 的调度。
需要注意的是,sleep()
可能会抛出 InterruptedException
,因此在调用时通常需要处理该异常。
public class ThreadSleepExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 开始休眠");
Thread.sleep(3000); // 线程休眠3秒
System.out.println(Thread.currentThread().getName() + " 休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
}
}
在这个例子中,线程通过 Thread.sleep()
休眠了 3 秒钟,然后继续执行剩下的代码。sleep()
的休眠时间是最低限制,也就是说,线程休眠的时间可能会比指定的时间稍长,因为休眠结束后线程需要等待系统的调度。
以上是关于如何启动、等待、获取当前线程、休眠和中断线程的详细解释,这些操作都是 Java 多线程编程中常用的基本方法,希望读者可以根据上述的案例以及讲解对Java中的Thread的类有自己独到的理解!
以上就是本篇文章的全部内容了~~~