一、认识线程-Thread类
一个线程就是一个“执行流”。每个线程都可以按照顺序执行自己的代码,而多个线程可以 "同时" 执行多份代码。
就相当于:
⼀家公司要去银⾏办理业务,既要进⾏财务转账,⼜要进⾏福利发放,还得进⾏缴社保。 如果只有张三⼀个会计就会忙不过来,耗费的时间特别⻓。为了让业务更快的办理好,张三⼜找来两 位同事李四、王五⼀起来帮助他,三个⼈分别负责⼀个事情,分别申请⼀个号码进⾏排队,⾃此就有 了三个执⾏流共同完成任务,但本质上他们都是为了办理⼀家公司的业务。 此时,我们就把这种情况称为多线程,将⼀个⼤任务分解成不同⼩任务,交给不同执⾏流就分别排队 执⾏。其中李四、王五都是张三叫来的,所以张三⼀般被称为主线程(Main Thread)。
线程本身是操作系统中的概念。操作系统内核实现了线程这样的机制,并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库)。而Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装。
Thread 类是 JVM 用来管理线程的一个类。在Java中,每个线程执行流都是通过Thread类的对象来描述的。JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
1、Thread类的常见构造方法
1.hread t1 = new Thread();
2.Thread t2 = new Thread(new MyRunnable());
3.Thread t3 = new Thread("这是我的名字");
4.Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
2、Thread 的几个常见属性
• ID 是线程的唯⼀标识,不同线程不会重复
• 名称是各种调试⼯具⽤到
• 状态表⽰线程当前所处的⼀个情况,下⾯我们会进⼀步说明
• 优先级⾼的线程理论上来说更容易被调度到
• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
• 是否存活,即简单的理解,为 run ⽅法是否运⾏结束了
• 线程的中断问题,下⾯我们进⼀步说明
二、Thread类的基本用法
1、创建线程
方法一 继承 Thread 类
1.继承 Thread 来创建⼀个线程类,在 MyThread类 中重写 run() 入口方法
class MyThread extends Thread {
@Override
public void run() {
System.out.println("这⾥是线程运⾏的代码");
}
}
2.创建 MyThread 类的实例
MyThread t = new MyThread();
3.调⽤ start ⽅法启动线程
t.start(); // 线程开始运⾏
运行结果
方法二 实现 Runnable 接口
1. 实现 Runnable 接⼝
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这⾥是线程运⾏的代码");
}
}
2. 创建 Thread 类实例, 调⽤ Thread 的构造⽅法时将 Runnable 对象作为 target 参数.
Thread t = new Thread(new MyRunnable());
调⽤ start ⽅法
t.start(); // 线程开始运行
对⽐上⾯两种⽅法: 继承 Thread 类, 直接使⽤ this 就表⽰当前线程对象的引⽤.
实现 Runnable 接⼝, this 表⽰的是 MyRunnable 的引⽤. 需要使⽤ Thread.currentThread()
在Java中,有两种常见的创建线程的方式:继承Thread类和实现Runnable接口。
- 继承Thread类: 当你继承Thread类创建线程时,this关键字代表当前线程对象的引用。通过this可以直接访问当前线程对象的方法和属性。
public class MyThread extends Thread {
public void run() {
// 在这里编写线程执行的代码
System.out.println("This is a thread extending Thread class");
}
}MyThread myThread = new MyThread();
myThread.start();
- 实现Runnable接口: 当你实现Runnable接口创建线程时,this关键字代表实现了Runnable接口的类的对象引用。因为Runnable接口不是线程本身,而是一个任务,需要将其传递给Thread对象来创建线程。
public class MyRunnable implements Runnable {
public void run() {
// 在这里编写线程执行的代码
System.out.println("This is a thread implementing Runnable interface");
}
}MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
Thread.currentThread()
是一个静态方法,它返回当前正在执行的线程对象的引用。这个方法可以在任何地方调用,无论是在继承Thread类还是实现Runnable接口的类中。Thread.currentThread()
方法的返回值是一个Thread对象,代表当前执行的线程。
匿名内部类创建 Thread 子类对象
1.匿名内部类创建 Thread ⼦类对象
// 使⽤匿名类创建 Thread ⼦类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使⽤匿名类创建 Thread ⼦类对象");
}
};
2.匿名内部类创建 Runnable ⼦类对象
// 使⽤匿名类创建 Runnable ⼦类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使⽤匿名类创建 Runnable ⼦类对象");
}
});
匿名内部类创建 Runnable 子类对象(最常用使用)
// 使⽤ lambda 表达式创建 Runnable ⼦类对象
Thread t3 = new Thread(() -> System.out.println("使⽤匿名类创建 Thread ⼦类对象"));
Thread t4 = new Thread(() -> {
System.out.println("使⽤匿名类创建 Thread ⼦类对象");
});
2、启动线程 start()
(1)start()方法
start()方法是启动线程的方法,调用该方法会使线程进入就绪状态,等待CPU分配时间片后开始执行。run()方法是线程的执行体,它包含了线程要执行的代码;当调用start()方法启动线程后,线程会在独立的执行路径上自动执行run()方法中的代码(也就是说,当调用start()启动线程后,系统会自动调用run()方法执行线程的执行体)。
但要注意的是,run()方法并不标识新的线程的创建;调用 start 方法,才真的在操作系统的底层创建出了一个线程。
start()方法的使用:在创建完线程后,通过线程的实例调用start()即可。
(2)start()方法与run()方法的区别
在Thread类中,run()方法是线程的执行体,包含了线程要执行的具体任务代码。而start()方法是启动线程的方法,调用start()方法会创建一个新的线程并执行run()方法中的代码。
从方法的区别来看:
- run()方法是Thread类中的一个普通方法,可以被直接调用,但不会创建新的线程来执行其中的代码。
- start()方法是Thread类中的一个特殊方法,调用start()方法会创建一个新的线程,并在新线程中执行run()方法中的代码。
从运行结果的区别来看:
- 如果直接调用run()方法,代码会在当前线程中顺序执行,不会创建新的线程。
- 如果调用start()方法,会创建一个新的线程,新线程会并发执行run()方法中的代码,实现多线程并发执行的效果。
(3)start()方法与run()方法的区别--总结
简单来说,直接调用run()方法不会创建新线程,代码在当前线程中执行;而调用start()方法会创建新线程,并在新线程中执行代码,实现多线程并发执行的效果。
3、线程中断 interrupt()
线程的中断就是字面意思:让一个线程停下来。也即线程的终止。
本质上来说,让一个线程终止的唯一方法是让该线程的入口方法run()执行完毕。
(1)给线程中设定一个结束标志位 isQuit
可以给线程设置一个结束标志位
isQuit
,以便在适当的时候结束线程的执行。
(2)注意 isQuit 的书写位置
isQuit
的书写位置应该是在线程的run()
方法中进行检查,以决定是否终止线程的执行。
(3)使用Thread类内置的标志位 isInterrupted()
可以使用
Thread
类内置的标志位isInterrupted()
来检查线程是否被中断。
(4)interrupt() 方法的作用
调用
interrupt()
方法可以设置线程的中断状态,但实际上并不会立即中断线程的执行,而是设置一个中断标志位,由线程自行决定如何响应中断。
(5)为什么sleep()要清空标志位呢?
在使用
sleep()
方法时,通常会在捕获InterruptedException
异常后清空中断标志位,这是因为sleep()
方法在捕获到中断信号时会抛出InterruptedException
异常,并且会清除中断状态,如果不清除中断状态,线程将无法正确响应后续的中断信号。
以下是一个简单的Java代码示例,演示了如何在线程中使用中断标志位isQuit
和isInterrupted()
方法来实现线程中断:
public class MyThread extends Thread {
private volatile boolean isQuit = false;
@Override
public void run() {
while (!isQuit) {
System.out.println("Thread is running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread is interrupted!");
// 清空中断状态
Thread.currentThread().interrupt();
}
}
System.out.println("Thread is stopped.");
}
public void stopThread() {
isQuit = true;
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
// 模拟在一定时间后中断线程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stopThread(); // 设置结束标志位
thread.interrupt(); // 中断线程
}
}
在这个例子中,线程会每隔1秒输出一次"Thread is running...",在5秒后会调用stopThread()方法设置结束标志位isQuit为true,同时调用interrupt()方法中断线程。在run()方法中,通过检查isQuit标志位和捕获InterruptedException异常来实现线程的中断。
4、线程等待 join()
在Java中,线程的join()
方法也可以用来等待线程执行完毕。当调用一个线程的join()
方法时,当前线程将会被阻塞,直到被调用的线程执行完毕。
(1)join()方法,无参数
当join()
方法没有参数时,当前线程会一直等待被调用的线程执行完毕。
(2)join()方法,带参数
当join(long millis)
方法带有参数时,可以设置一个超时时间(单位为毫秒),如果被调用的线程在超时时间内没有执行完毕,当前线程将会继续执行。具体语法如下:
thread.join(timeout);
其中,timeout
是等待的最大时间,如果超过这个时间被调用的线程仍未执行完毕,当前线程将不再等待,继续执行后续代码。
public class JoinExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("Thread 1 is running");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 is finished");
});
Thread thread2 = new Thread(() -> {
System.out.println("Thread 2 is running");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2 is finished");
});
thread1.start();
thread2.start();
try {
thread1.join(); // 等待thread1执行完毕
thread2.join(1500); // 等待最多1.5秒,如果超时则不再等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread is finished");
}
}
在上面的示例中,主线程启动了两个子线程thread1
和thread2
,然后分别调用了join()
方法来等待这两个线程执行完毕。可以自己尝试一下。
5、线程休眠 sleep()
sleep()线程休眠的方法前面已经使用过。需要注意的有两点,一是该方法是Thread类的一个静态方法,由Thread类名直接调用:Thread.sleep();二是该方法存在一个受查异常,在使用这个方法时,需要try-catch或throw来处理这个异常。还有⼀点要记得,因为线程的调度是不可控的,所以,这个⽅法只能保证 实际休眠时间是⼤于等于参数设置的休眠时间的。
6、获取线程实例 currentThread()
能获取当前线程对象的实例。在哪个线程里调用,得到的就是哪个线程对象的实例。需要的注意的是,该方法是Thread类的静态方法,由Thread类直接调用。
public class demo7 { public static void main(String[] args) { Thread thread = Thread.currentThread(); //获取当前对象实例 System.out.println(thread.getName()); } }