Java 线程的使用

线程相关概念

程序 (Program)

  • 程序是指平常使用的静态程序。

进程 (Process)

  • 进程是操作系统中正在运行的动态程序。

线程 (Thread)

  • 线程是程序内部的一条执行流程或路径。

协程 (Coroutine)

  • Go 语言特性。
  • Java 21 版本引入虚线程,更加轻量级(占用资源如 CPU、内存、IO 更少)。
    • Platform Thread 平台线程(基于操作系统)。
    • Virtual Thread 虚线程(基于 JVM)。

进程和线程关系

  • 一个进程中包含一个或者多个线程。
  • 一个线程一定从属于某一个进程。

Java 中的线程 (Thread)

如何创建线程

创建线程方式一

继承 Thread 类并重写 run 方法,创建对象后调用 start 方法。

1
2
3
4
5
6
7
8
9
10
11
12
public class MyThread extends Thread {

@Override
public void run() {
System.out.println("MyThread is running");
}

public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 这行代码执行时,JVM 会向操作系统申请内核线程与 Java 中的线程对象对应
}
}

创建线程方式二

实现 Runnable 接口并重写 run 方法,创建线程时将 Runnable 对象作为参数传递给 Thread 类的构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyRunnable implements Runnable {

@Override
public void run() {
System.out.println("MyRunnable is running");
}

public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start(); // 启动线程
}
}

创建线程方式三

利用 Callable 接口和 FutureTask 类。Callable 接口可以有返回值,并且可以抛出异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<String> {

@Override
public String call() throws Exception {
return "MyCallable is running";
}

public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread t1 = new Thread(futureTask);
t1.start(); // 启动线程

try {
// 获取线程执行结果
String result = futureTask.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获取执行当前线程的名称
System.out.println(Thread.currentThread().getName());

// 启动线程
thread.start();

// 获取当前线程对象
Thread current = Thread.currentThread();

// 获取线程名称
String name = thread.getName();

// 设置线程名称
thread.setName("NewThreadName");

// 让当前线程休眠指定时间(毫秒)
Thread.sleep(1000);

// 等待线程执行完成
thread.join();

线程安全问题

什么是线程安全问题

  • 当多个线程同时访问和修改同一个共享资源时,可能会导致数据不一致或程序行为异常,这种现象被称为线程安全问题。

演示线程安全问题现象

以下代码展示了多个线程同时操作共享资源(银行账户余额)时可能导致数据不一致的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class BankAccount {
private int balance = 1000;

public void withdraw(int amount) {
if (balance >= amount) {
try {
// 模拟取钱操作的延迟
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取钱成功,余额:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 取钱失败,余额不足!");
}
}

public static void main(String[] args) {
BankAccount account = new BankAccount();

Runnable task = () -> {
for (int i = 0; i < 3; i++) {
account.withdraw(400);
}
};

Thread t1 = new Thread(task, "线程1");
Thread t2 = new Thread(task, "线程2");

t1.start();
t2.start();
}
}

解决线程安全问题的方案 (管程) Monitor

线程同步方案

synchronized 关键字

  • synchronized 是 Java 提供的内置锁机制,用于实现线程同步。
  • 可以用于同步代码块或同步方法,确保同一时刻只有一个线程可以访问同步区域。
同步代码块
  • 锁对象可以是 this(当前实例)、class(当前类的 Class 对象)。
1
2
3
4
5
6
7
8
9
10
11
public void method() {
synchronized (this) {
// 同步代码块,锁对象是当前实例
}
}

public static void staticMethod() {
synchronized (MyClass.class) {
// 同步代码块,锁对象是当前类的 Class 对象
}
}
同步方法
  • 实例方法的锁对象是 this,静态方法的锁对象是当前类的 Class 对象。
1
2
3
4
5
6
7
public synchronized void instanceMethod() {
// 同步实例方法,锁对象是当前实例
}

public static synchronized void staticMethod() {
// 同步静态方法,锁对象是当前类的 Class 对象
}

ReentrantLock
  • 可重入锁,支持公平锁和非公平锁。
  • 提供了 tryLock() 方法,可以尝试获取锁而不会阻塞线程。
1
2
3
4
5
6
7
8
9
10
11
Lock lock = new ReentrantLock();

if (lock.tryLock()) {
try {
// 获取锁后执行的代码
} finally {
lock.unlock();
}
} else {
// 未获取锁时的处理
}

线程通信

  • 内容待补充…

线程池

什么是线程池

  • 线程池是一种可以复用线程的技术。

如何创建线程池

使用 ThreadPoolExecutor 创建线程池

  • ThreadPoolExecutor 提供了更灵活的线程池配置,构造方法的所有参数如下:

    • corePoolSize:核心线程数,线程池中始终保持存活的线程数量,即使它们处于空闲状态。
    • maximumPoolSize:最大线程数,线程池中允许的最大线程数量,当任务队列已满时会创建新线程,直到达到此值。
    • keepAliveTime:空闲线程存活时间,当线程数超过核心线程数时,多余的空闲线程在等待新任务时最多存活的时间。
    • unit:时间单位,用于指定 keepAliveTime 的时间单位,例如 TimeUnit.SECONDS
    • workQueue:任务队列,用于存放等待执行的任务,常见实现包括:
      • ArrayBlockingQueue:一个有界的阻塞队列。
      • LinkedBlockingQueue:一个可选有界的阻塞队列。
      • SynchronousQueue:一个不存储元素的队列,每个插入操作必须等待一个移除操作。
    • threadFactory:线程工厂,用于创建线程,通常用于设置线程名称或是否为守护线程。
    • handler:拒绝策略,当任务无法被执行时的处理方式,常见策略包括:
      • AbortPolicy:默认策略,直接抛出 RejectedExecutionException 异常。
      • CallerRunsPolicy:调用线程执行任务。
      • DiscardPolicy:直接丢弃任务,不抛出异常。
      • DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试重新提交任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomThreadPoolExample {
public static void main(String[] args) {
// 创建自定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(10), // 任务队列
r -> new Thread(r, "CustomThread"), // 自定义线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

// 提交任务到线程池
for (int i = 0; i < 8; i++) {
final int task = i;
threadPool.execute(() -> {
System.out.println("执行任务 " + task + " 的线程:" + Thread.currentThread().getName());
});
}

// 关闭线程池
threadPool.shutdown();
}
}

使用 Executors 工具类创建线程池

  • Executors 提供了多种便捷方法来创建线程池。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

// 提交任务到线程池
for (int i = 0; i < 5; i++) {
final int task = i;
fixedThreadPool.execute(() -> {
System.out.println("执行任务 " + task + " 的线程:" + Thread.currentThread().getName());
});
}

// 关闭线程池
fixedThreadPool.shutdown();
}
}

在线程池中执行任务

执行 Runnable 任务

  • 使用线程池执行 Runnable 任务时,可以通过 execute 方法提交任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RunnableTaskExample {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);

Runnable task = () -> {
System.out.println("执行 Runnable 任务的线程:" + Thread.currentThread().getName());
};

threadPool.execute(task);

threadPool.shutdown();
}
}

执行 Callable 任务

  • 使用线程池执行 Callable 任务时,可以通过 submit 方法提交任务,并通过返回的 Future 对象获取结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableTaskExample {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);

Callable<String> task = () -> {
return "执行 Callable 任务的线程:" + Thread.currentThread().getName();
};

try {
Future<String> future = threadPool.submit(task);
System.out.println(future.get()); // 获取任务执行结果
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}

线程池工具类 Executors

  • 内容待补充…