线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
一个线程就是一个"执行流", 每个线程可以按照顺序执行自己的代码, 多个线程之间"同时"执行着多份代码
虽然多进程也能实现并发编程, 但是多线程要比多进程更轻量
- 创建线程比创建进程更快
- 销毁线程比销毁进程更快
- 调度线程比调度进程更快
线程是操作系统中的概念, 操作系统内核实现了这样的机制,并且对用户层提供了一些 API 供用户使
用(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装
下面我们写一个简单的多线程程序, 先不关心它是怎么写的, 就先简单的了解一下多线程,
感受多线程程序和单线程程序的区别
package Thread;
import java.util.Random;
public class ThreadDemo1 {
private static class MyThread extends Thread {
@Override
public void run() {
Random random = new Random();
while (true) {
// 打印线程的名称
System.out.println(Thread.currentThread().getName());
try {
// 随机停止运行 0 ~ 9 秒
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
// 启动线程
thread1.start();
thread2.start();
thread3.start();
Random random = new Random();
while (true) {
System.out.println(Thread.currentThread().getName());
try {
// 随机停止运行 0 ~ 9 秒
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
下面我们用jconsole观察线程
首先我们打开jconsole
public class ThreadDemo2 {
// 1.继承一个Thread 类来创建一个线程类
class MyThread extends Thread {
@Override
// 重写里面的run方法
public void run() {
System.out.println("这里是线程运行的代码");
}
}
public static void main(String[] args) {
// 2.创建一个 MyThread 实例
MyThread thread1 = new MyThread();
// 3. 调用 start 方法启动线程
thread1.start();
}
}
public class ThreadDemo3 {
// 1. 实现Runnable接口
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这里是线程执行的代码");
}
}
public static void main(String[] args) {
// 2. 创建Thread类实例, 调用Thread类的构造方法时将Runnable对象作为target 参数
Thread thread1 = new Thread(new MyRunnable());
// 启动线程
thread1.start();
}
}
这里我们要注意的是:
实现Runnable接口,并不能直接启动或者说实现一个线程,Runnable接口和线程是两个不同的概念
换句话说,一个类,实现Runnable接口,这个类可以做很多事情,不仅仅只被用于线程,也可以用于其他功能!
public class ThreadDemo5 {
public static void main(String[] args) {
Thread thread1 = new Thread() {
@Override
public void run() {
System.out.println("使用匿名内部类创建 Thread 子类对象");
}
};
//启动线程
thread1.start();
}
}
public class ThreadDemo6 {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名内部类创建 Runnable 子类对象");
}
});
// 启动线程
thread1.start();
}
}
public class ThreadDemo7 {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("使用匿名内部类创建 Thread 对象");
});
// 启动线程
thread1.start();
}
}
注:
Thread 类中run()和start()方法的区别
作用功能不同:
run方法的作用是描述线程具体要执行的任务;
start方法的作用是真正的去申请系统线程
运行结果不同:
run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。
多线程的主要优势之一就是增加运行速度
下面我们举个例子具体看
我们使用并发和串行方式计算 a 和 b 的值, 分别让 a 和 b 自加20_0000_0000次,
/**
* @describe
* @author chenhongfei
* @version 1.0
* @date 2023/9/23
*/
package Thread;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class ThreadDemo8 {
private static final long count = 20_0000_0000;
public static void main(String[] args) throws InterruptedException {
// 使用并行方式
concurrency();
// 使用串行方式
serial();
}
// 并行
private static void concurrency() throws InterruptedException {
long begin = System.nanoTime();
// 利用一个线程计算 a 的值
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (int i = 0; i < count; i++) {
a++;
}
}
});
//启动线程
thread1.start();
// 主线程内计算b的值
int b = 0;
for (int i = 0; i < count; i++) {
b++;
}
// 等待Thread线程结束
thread1.join();
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 /1000; //1 纳秒=1e-6 毫秒
System.out.printf("并发: %f 毫秒%n",ms);
}
// 串行
private static void serial() {
// 全在主线程内计算a, b 的值
long begin = System.nanoTime();
int a = 0;
int b = 0;
for (int i = 0; i < count; i++) {
a++;
}
for (int i = 0; i < count; i++) {
b++;
}
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 /1000; //1 纳秒=1e-6 毫秒
System.out.printf("串行: %f 毫秒%n",ms);
}
}
Thread类是Java中用于创建线程的类。线程是程序执行的最小单元,它允许多个任务并发执行,提高了程序的效率。Thread类的实例表示一个独立的执行线程,可以通过继承Thread类或实现Runnable接口来创建线程。Thread类提供了一系列方法来管理线程的状态和行为,比如启动线程、暂停线程、恢复线程、等待线程完成等。在Java中,线程的使用非常普遍,比如在网络编程、多线程服务器、GUI应用程序等领域都会用到线程。
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriorty() |
是否后台线程 | isDaemom() |
是否存活 | isAlive() |
是否被中断 | isInterrupt() |
具体情况我们参考下面代码
/**
* @describe
* @author chenhongfei
* @version 1.0
* @date 2023/9/23
*/
package Thread;
public class ThreadDemo9 {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "我还活着");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "我即将死去");
});
System.out.println(Thread.currentThread().getName() + ": ID" + thread1.getId());
System.out.println(Thread.currentThread().getName() + ": 名称" + thread1.getName());
System.out.println(Thread.currentThread().getName() + ": 状态" + thread1.getState());
System.out.println(Thread.currentThread().getName() + ": 优先级" + thread1.getPriority());
System.out.println(Thread.currentThread().getName() + ": 后台线程" + thread1.isDaemon());
System.out.println(Thread.currentThread().getName() + ": 活着" + thread1.isAlive());
System.out.println(Thread.currentThread().getName() + ": 被中断" + thread1.isInterrupted());
//启动线程
thread1.start();
while (thread1.isAlive()) {
System.out.println(Thread.currentThread().getName() + ": 状态" + thread1.getState());
}
}
}
在Java中,start()方法通常用于启动线程。它是定义在Thread类中的方法,用于启动线程。
当调用start()方法时,线程会从它的run()方法开始执行。run()方法是线程的执行体,它包含了线程要执行的代码。
当线程启动后,它将从run()方法开始执行,直到该方法结束或线程被中止。在执行期间,线程可以访问共享变量和对象,并且可以与其他线程并发执行。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 线程执行的代码(重写run 方法)
System.out.println("Thread started.");
}
});
thread.start(); // 启动线程
在上面的示例中,我们创建了一个新的线程对象,将一个实现了Runnable接口的对象传递给了构造函数。在run()方法中,我们打印了一条消息表示线程已经启动。然后,我们调用start()方法来启动线程。
需要注意的是,start()方法并不会阻塞调用它的线程。启动线程后,程序会继续执行其他代码。如果需要等待线程执行结束后再继续执行当前线程,可以使用join()方法。
一个线程一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那该如何通知这个线程停止呢?这就涉及到我们的停止线程的方式了。
下面我们介绍两种方式:
通过设置一个标志位,线程在执行时可以检查这个标志位,如果发现标志位被设置了,那么线程就认为自己被中断了。
下面是一个简单的使用标志位中断线程的实例:
public class ThreadDemo10 {
private static volatile boolean isInterrupted = false; //使用volatile 修饰, 保证线程间的可见性
static Thread thread1 = new Thread(() -> {
while (!isInterrupted) {
System.out.println(Thread.currentThread().getName() + "开始转账");
try {
Thread.sleep(1000); // 模拟任务耗时
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "取消转账, 差点误了大事");
});
public static void main(String[] args) {
thread1.start(); // 线程启动, 开始转账
isInterrupted = true; // 取消转账
}
}
我们也可以使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记
下面先介绍几个方法
方法 | 说明 |
---|---|
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位 |
public static booleaninterrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public booleanisInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
public class ThreadDemo11 {
static Thread thread1 = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) { // Thread.currentThread().isInterrupted() 这就是标志位
System.out.println(Thread.currentThread().getName() + "开始转账");
try {
Thread.sleep(1000); // 模拟任务耗时
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "取消转账, 差点误了大事");
});
public static void main(String[] args) {
thread1.start(); // 线程启动, 开始转账
thread1.interrupt(); //设置标志位 取消转账
}
}
线程收到通知的方法有两种:
当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程.
标志位就相当于一个开关
Thread.isInterrupted() 相当于按下开关, 开关自动弹起来了, 这个称为"清楚标志位"
Thread.currentThread().isInterrupted() 相当于按下开关, 开关不弹起来, 这个称为"不清除标志位"
public class ThreadDemo12 {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread1.start();
thread1.interrupt(); // 通知中断线程, 设置标志位
}
}
只有第一个是 true , 后面都是 false 因为"开关弹回去了"
public class ThreadDemo13 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().isInterrupted());
}
});
thread.start();
thread.interrupt();
}
}
有时候, 我们经常需要等待一个线程完成它的工作后, 才能进行下一步工作, 比如说, 这个月发工资我要买个手机, 只有等到了工资到账, 才能去买手机, 这时候我们就需要明确等待线程的结束.
每个Thread实例都有一个join()方法,该方法使得当前正在执行的线程暂停执行(阻塞),直到被join的线程执行结束(即从run()方法返回)。
public class ThreadDemo14 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("还有" + (5-i) +"天发工资");
}
System.out.println("工资已到账");
});
Thread thread2 = new Thread(() -> {
System.out.println("正在去买手机的路上");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("买到了手机");
});
thread1.start();
thread1.join(); //直到 thread1 执行结束才执行后面内容
thread2.start();
thread2.join();
System.out.println("此任务结束");
}
}
在这个例子中,主线程(Main Thread)会等待thread1线程执行结束,然后再执行自己的后续操作(让thread2执行)。join()方法的使用使得主线程能够同步地执行与子线程的结束。
如果把两个join() 注释掉, 效果如下:
此外还可以使用wait(), notify() 和 notifyAll(), 这个后面我们细说.
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
}
}
currentThread() 现在就是指向当前执行线程的一个引用。你可以用这个引用来获取当前线程的各种信息,例如它的名称、它的优先级、它是否是守护线程等等。
System.out.println("Current thread name: " + currentThread.getName());
System.out.println("Current thread priority: " + currentThread.getPriority());
System.out.println("Is current thread a daemon?: " + currentThread.isDaemon());
我们可以使用Thread.sleep()方法来使当前线程休眠(暂时停止执行)一段时间。这个方法接受一个以毫秒为单位的时间参数,指定线程应该休眠的时间。
public class ThreadDemo15 {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println(System.currentTimeMillis());
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(System.currentTimeMillis());
});
thread1.start();
}
}