Java多线程全面解析

1. 多线程基础概念

1.1 进程与线程

进程是操作系统进行资源分配和调度的基本单位,每个进程都有独立的内存空间和系统资源。一个进程崩溃不会影响其他进程的运行,提供了良好的隔离性。

线程是进程中的一个执行单元,是CPU调度和执行的最小单位。同一进程内的所有线程共享进程的内存空间和系统资源,这使得线程间的通信更加高效,但也带来了数据同步的挑战。

1.2 为什么需要多线程

  1. 提高程序响应性:对于图形界面应用程序,使用多线程可以避免用户界面”冻结”
  2. 提高CPU利用率:当某个线程等待I/O操作时,其他线程可以继续执行,充分利用CPU资源
  3. 简化程序结构:对于需要处理多个独立任务的程序,多线程可以提供更清晰的设计

1.3 多线程的挑战

  • 线程安全问题:多个线程同时访问共享数据可能导致数据不一致
  • 死锁问题:线程间相互等待对方释放锁,导致程序无法继续执行
  • 性能问题:线程上下文切换和同步操作会带来额外的开销

2. 线程创建方式

2.1 继承Thread类

这是最简单的创建线程方式,通过继承Thread类并重写run()方法。但这种方式的缺点是Java不支持多重继承,如果已经继承了其他类,则无法使用这种方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行: " + Thread.currentThread().getName());
}

public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start(); // 启动线程
thread2.start();

// 注意:直接调用run()方法不会创建新线程,而是在当前线程中执行
}
}

2.2 实现Runnable接口

这种方式更灵活,因为一个类可以实现多个接口。Runnable对象可以被多个线程共享,更适合多个线程处理同一资源的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行: " + Thread.currentThread().getName());
}

public static void main(String[] args) {
// 创建Runnable实例
MyRunnable runnable = new MyRunnable();

// 创建线程并传入Runnable实例
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}

2.3 实现Callable接口

Callable与Runnable类似,但可以返回执行结果和抛出异常。通常与FutureTask配合使用,FutureTask可以获取线程的执行结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 {
Thread.sleep(1000);
return "执行结果: " + Thread.currentThread().getName();
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();

// 获取返回值(会阻塞直到任务完成)
String result = futureTask.get();
System.out.println(result);
}
}

2.4 Lambda表达式

Java 8引入的Lambda表达式使线程创建更加简洁,特别适合简单的异步任务。

1
2
3
4
5
6
7
8
public class LambdaThread {
public static void main(String[] args) {
// 实现Runnable
new Thread(() -> {
System.out.println("Lambda线程: " + Thread.currentThread().getName());
}).start();
}
}

3. 线程生命周期

Java线程的生命周期包含6种状态,这些状态在Thread.State枚举中定义:

3.1 新建状态(NEW)

当线程对象被创建但尚未调用start()方法时,线程处于新建状态。

3.2 可运行状态(RUNNABLE)

调用start()方法后,线程进入可运行状态。注意,这并不意味着线程正在执行,只是表示线程已经准备好执行,等待CPU分配时间片。

3.3 阻塞状态(BLOCKED)

当线程试图获取一个内部对象锁(不是java.util.concurrent库中的锁),而该锁被其他线程持有,则该线程进入阻塞状态。

3.4 等待状态(WAITING)

线程等待另一个线程执行特定操作,如调用Object.wait()或Thread.join()方法。

3.5 超时等待状态(TIMED_WAITING)

与WAITING状态类似,但带有超时时间,如Thread.sleep()或Object.wait(timeout)。

3.6 终止状态(TERMINATED)

线程执行完成或因异常退出run()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000); // TIMED_WAITING状态
} catch (InterruptedException e) {
e.printStackTrace();
}
});

System.out.println("新建状态: " + thread.getState()); // NEW

thread.start();
System.out.println("启动后状态: " + thread.getState()); // RUNNABLE

Thread.sleep(100);
System.out.println("睡眠中状态: " + thread.getState()); // TIMED_WAITING

thread.join();
System.out.println("结束后状态: " + thread.getState()); // TERMINATED
}
}

4. 线程同步与锁机制

4.1 线程安全问题

当多个线程同时访问共享资源时,可能会产生不一致的结果。例如,多个线程同时对同一个计数器进行递增操作,由于递增操作不是原子操作,可能会导致计数结果不正确。

4.2 synchronized关键字

synchronized是Java内置的同步机制,可以用于方法或代码块。

同步方法:锁对象是当前实例(对于实例方法)或类对象(对于静态方法)
同步代码块:可以指定任意对象作为锁,更加灵活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SynchronizedDemo {
private int count = 0;
private final Object lock = new Object();

// 同步方法
public synchronized void increment() {
count++;
}

// 同步代码块
public void incrementWithBlock() {
synchronized (lock) {
count++;
}
}
}

4.3 ReentrantLock

ReentrantLock是java.util.concurrent.locks包中的锁实现,提供了比synchronized更灵活的锁操作:

  1. 可重入性:同一个线程可以多次获取同一把锁
  2. 可中断:等待锁的线程可以被中断
  3. 公平性:可以选择公平锁或非公平锁
  4. 条件变量:可以创建多个Condition对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();

public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 确保锁释放
}
}
}

4.4 读写锁(ReadWriteLock)

对于读多写少的场景,使用读写锁可以提高性能。读锁是共享的,写锁是独占的。

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
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
private int data = 0;
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

public void readData() {
rwLock.readLock().lock();
try {
System.out.println("读取数据: " + data);
} finally {
rwLock.readLock().unlock();
}
}

public void writeData(int newValue) {
rwLock.writeLock().lock();
try {
data = newValue;
System.out.println("写入数据: " + data);
} finally {
rwLock.writeLock().unlock();
}
}
}

5. 线程间通信

5.1 wait/notify机制

wait()、notify()和notifyAll()是Object类的方法,用于线程间通信。这些方法必须在同步代码块或同步方法中使用。

  • wait(): 使当前线程等待,并释放对象锁
  • notify(): 唤醒一个正在等待该对象锁的线程
  • notifyAll(): 唤醒所有正在等待该对象锁的线程
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
public class WaitNotifyDemo {
private boolean flag = false;

public synchronized void produce() {
while (flag) {
try {
wait(); // 等待消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产产品...");
flag = true;
notify(); // 通知消费者
}

public synchronized void consume() {
while (!flag) {
try {
wait(); // 等待生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费产品...");
flag = false;
notify(); // 通知生产者
}
}

5.2 使用BlockingQueue

BlockingQueue是java.util.concurrent包中的接口,提供了线程安全的队列操作,非常适合生产者-消费者模式。

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
35
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ProducerConsumerDemo {
private static final int CAPACITY = 5;
private static BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(CAPACITY);

static class Producer extends Thread {
public void run() {
try {
for (int i = 0; i < 10; i++) {
queue.put(i);
System.out.println("生产: " + i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

static class Consumer extends Thread {
public void run() {
try {
for (int i = 0; i < 10; i++) {
int value = queue.take();
System.out.println("消费: " + value);
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

6. 线程池

6.1 为什么使用线程池

  1. 降低资源消耗:减少创建和销毁线程的开销
  2. 提高响应速度:任务到达时可以直接使用已有线程
  3. 提高线程可管理性:可以统一分配、调优和监控线程

6.2 Executor框架

Java通过Executor框架提供线程池功能,主要接口和类包括:

  • Executor: 执行已提交的Runnable任务的对象
  • ExecutorService: 扩展了Executor接口,提供了生命周期管理
  • ThreadPoolExecutor: 线程池的核心实现类
  • Executors: 线程池工厂类,提供常用的线程池配置
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
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

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

// 提交任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("执行任务 " + taskId + ",线程: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}

// 关闭线程池
executor.shutdown();
try {
// 等待所有任务完成
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}

6.3 线程池参数详解

ThreadPoolExecutor的构造函数包含多个重要参数:

  1. corePoolSize:核心线程数,即使线程空闲也不会被回收
  2. maximumPoolSize:最大线程数,线程池中允许的最大线程数量
  3. keepAliveTime:空闲线程存活时间
  4. unit:keepAliveTime的时间单位
  5. workQueue:工作队列,用于保存等待执行的任务
  6. threadFactory:线程工厂,用于创建新线程
  7. handler:拒绝策略,当任务太多无法处理时的策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.concurrent.*;

public class CustomThreadPool {
public static void main(String[] args) {
// 创建自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲线程存活时间
new ArrayBlockingQueue<>(10), // 工作队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
}
}

7. 高级并发工具类

7.1 CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。计数器只能减少不能增加,是一次性使用的。

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

public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);

for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程" + Thread.currentThread().getName() + "执行完成");
latch.countDown(); // 计数器减1
}).start();
}

latch.await(); // 等待所有线程完成
System.out.println("所有子线程执行完毕,主线程继续执行");
}
}

7.2 CyclicBarrier

CyclicBarrier让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。与CountDownLatch不同,CyclicBarrier的计数器可以重置。

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
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("所有线程已到达屏障,执行屏障操作");
});

for (int i = 0; i < threadCount; i++) {
final int threadId = i;
new Thread(() -> {
try {
System.out.println("线程" + threadId + "准备就绪");
Thread.sleep(1000 * threadId);
System.out.println("线程" + threadId + "到达屏障点");
barrier.await(); // 等待其他线程
System.out.println("线程" + threadId + "继续执行");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}

7.3 Semaphore

Semaphore用来控制同时访问特定资源的线程数量,通过协调各个线程,保证合理使用公共资源。

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

public class SemaphoreDemo {
public static void main(String[] args) {
// 模拟3个停车位
Semaphore semaphore = new Semaphore(3);

// 模拟6辆汽车
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
System.out.println(Thread.currentThread().getName() + " 抢到车位");
Thread.sleep(2000); // 停车2秒
System.out.println(Thread.currentThread().getName() + " 离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
}, "汽车" + i).start();
}
}
}

7.4 CompletableFuture

Java 8引入的CompletableFuture提供了强大的异步编程能力,支持流式调用、组合多个异步任务等功能。

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

public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 异步执行任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello";
});

// 任务完成后处理结果
CompletableFuture<String> greeting = future.thenApply(result -> {
return result + " World";
});

// 阻塞获取结果
System.out.println(greeting.get()); // 输出: Hello World
}
}

总结

Java多线程编程是一个复杂但重要的主题。掌握多线程技术可以帮助开发出高性能、高并发的应用程序。在实际开发中,应该:

  1. 优先使用高级并发工具类,而不是直接使用底层同步机制
  2. 合理使用线程池,避免频繁创建和销毁线程
  3. 注意线程安全问题,适当使用同步机制保护共享资源
  4. 避免死锁,确保锁的获取和释放顺序一致
  5. 使用合适的并发工具解决特定的并发问题