1. 线程的基本概念
**线程(Thread)**是操作系统能够进行运算调度的最小单位,它是程序执行的基本单元。Java 是一种支持多线程的语言,允许多个线程同时运行,从而提高应用程序的响应能力和性能。
2. 创建线程的方式
Java 提供两种主要方式来创建线程:
2.1 继承 Thread
类
步骤:
- 创建一个类继承
Thread
。 - 重写
run()
方法。 - 创建线程对象并调用
start()
方法。
示例:
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Count: " + i);
try {
Thread.sleep(500); // 模拟工作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start(); // 启动线程
thread2.start(); // 启动另一个线程
}
}
解析:在这个例子中,两个线程同时运行,输出各自的计数。
2.2 实现 Runnable
接口
步骤:
- 创建一个类实现
Runnable
接口。 - 重写
run()
方法。 - 创建
Thread
对象并传入Runnable
实现。
示例:
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Count: " + i);
try {
Thread.sleep(500); // 模拟工作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
thread1.start(); // 启动线程
thread2.start(); // 启动另一个线程
}
}
解析:使用 Runnable
接口可以更灵活地使用线程,支持多个线程共享同一个 Runnable
实例。
3. 线程的生命周期
Java 线程的生命周期主要包括以下几个状态:
状态 | 描述 |
---|---|
新建 | 线程被创建,但尚未启动。 |
就绪 | 线程准备好执行,但可能因资源争用而被暂停。 |
运行 | 线程正在执行任务。 |
阻塞 | 线程等待某个资源(如 I/O 操作)。 |
死亡 | 线程执行完毕或异常退出。 |
状态转换示意:
- 从新建到就绪:调用
start()
方法。 - 从就绪到运行:操作系统调度线程运行。
- 从运行到阻塞:调用
wait()
、sleep()
、join()
等方法。 - 从运行到死亡:
run()
方法执行完毕或抛出异常。
4. 线程的同步
多线程编程中,多个线程可能会同时访问共享资源,这时需要进行同步,以避免数据不一致或冲突。
4.1 使用 synchronized
关键字
方法同步:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
解析:increment()
方法被 synchronized
修饰,确保同一时间只有一个线程可以访问该方法,避免数据竞争。
对象锁:
class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
解析:在这种方式下,使用 synchronized
块来控制访问范围,可以提高效率。
4.2 使用 Lock
接口
使用 java.util.concurrent.locks
包中的 Lock
接口提供更灵活的锁机制。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockCounter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 确保释放锁
}
}
public int getCount() {
return count;
}
}
解析:使用 ReentrantLock
提供显式锁,确保线程安全,并且在 finally
块中释放锁,避免死锁。
5. Java 并发包 java.util.concurrent
Java 提供的 java.util.concurrent
包包含许多用于构建并发程序的高级工具和类。
5.1 常用的并发工具
- Executor 框架:用于管理线程池,简化线程管理。
示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("Task is running in " + Thread.currentThread().getName());
});
}
executor.shutdown(); // 关闭线程池
}
}
解析:使用 ExecutorService
创建一个固定大小的线程池,提交任务后,线程池会管理线程的创建和销毁。
- Future:用于表示异步计算的结果。
示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
Thread.sleep(1000);
return 123; // 返回结果
});
System.out.println("Result: " + future.get()); // 阻塞,直到结果准备好
executor.shutdown();
}
}
解析:通过 Future
可以获取异步任务的执行结果,get()
方法会阻塞直到结果可用。
- CountDownLatch:用于实现线程间的等待机制。
示例:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
Runnable task = () -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " has completed.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 计数减一
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
latch.await(); // 等待计数器为0
System.out.println("All tasks completed.");
}
}
解析:CountDownLatch
允许主线程等待所有子线程完成后再继续执行。
5.2 线程安全的集合类
Java 提供了一些线程安全的集合类,常见的有:
ConcurrentHashMap
:一个高效的线程安全哈希表。
示例:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentMapExample {
public static void main(String[] args) {
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "One");
System.out.println(map.get(1));
map.putIfAbsent(2, "Two"); // 只有在键2不存在时才插入
System.out.println(map.get(2));
}
}
解析:ConcurrentHashMap
在多线程环境中安全地处理并发读写操作。
CopyOnWriteArrayList
:适合读多写少的场景。
示例:
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteExample {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
System.out.println(list);
list.remove("A"); // 安全删除
System.out.println(list);
}
}
解析:在读操作频繁且写操作少的情况下,CopyOnWriteArrayList
可以有效避免并发问题。
6. 线程的常用方法
start()
:启动线程,调用run()
方法。run()
:线程执行的方法,不能直接调用。join()
:等待线程结束,常用于主线程等待其他线程完成。
示例:
class JoinExample extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
public static void main(String[] args) throws InterruptedException {
JoinExample thread = new JoinExample();
thread.start();
thread.join(); // 主线程等待 JoinExample 完成
System.out.println("Main thread continues");
}
}
解析:主线程调用 join()
,会等待 JoinExample
线程执行完毕后再继续执行。
7. 线程池
使用线程池可以有效地管理线程资源,避免频繁创建和销毁线程的开销。
示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
int taskId = i;
pool.submit(() -> {
System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
pool.shutdown(); // 关闭线程池
}
}
解析:在这个例子中,使用线程池管理多个任务,并发执行,避免了创建线程的开销。
8. 并发编程中的常见问题
8.1 死锁
死锁是指两个或多个线程在执行过程中,因为争夺资源而造成的一种相互等待的现象。
示例:
class DeadlockExample {
static class A {
synchronized void methodA(B b) {
System.out.println("Thread 1: Holding lock A...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock B...");
b.last();
}
synchronized void last() {}
}
static class B {
synchronized void methodB(A a) {
System.out.println("Thread 2: Holding lock B...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock A...");
a.last();
}
synchronized void last() {}
}
public static void main(String[] args) {
final A a = new A();
final B b = new B();
new Thread(() -> a.methodA(b)).start();
new Thread(() -> b.methodB(a)).start();
}
}
解析:在这个例子中,两个线程相互持有对方的锁,导致死锁。
8.2 饥饿
饥饿是指某个线程长时间得不到执行,导致无法运行的现象。
示例:
import java.util.concurrent.locks.ReentrantLock;
public class StarvationExample {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Runnable task = () -> {
while (true) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " is running");
} finally {
lock.unlock();
}
}
};
new Thread(task).start();
new Thread(task).start();
}
}
解析:由于锁的竞争,某些线程可能一直无法获得锁,导致饥饿现象。
9. 总结
Java 提供了强大的线程和并发支持,通过核心类(Thread
、Runnable
、Lock
等)和 java.util.concurrent
包,可以方便地实现多线程编程。理解线程的生命周期、同步机制、并发工具,以及常见问题和解决方案,能够帮助开发者编写高效、安全的并发程序。
10. 进一步学习
建议深入了解 Java 并发的相关书籍,如《Java并发编程实战》,以获取更深入的知识和最佳实践。同时,通过实践项目来提高自己的并发编程能力。