线程是操作系统能够进行调度的最小单元,是一个程序的执行路径。一个进程可以包含多个线程,每个线程执行不同的任务,但它们共享相同的内存空间和资源。这使得线程之间的通信和协作更加高效,但也引发了线程同步和竞争条件等问题。
多线程编程允许在一个程序中并发执行多个任务,这有助于:
尽管多线程带来了很多优势,但它也带来了以下挑战:
在 Java 中,线程的创建主要有三种方式:
继承 Thread 类:
public class MyThread extends Thread {
public void run() {
System.out.println("Thread is running...");
}
}
MyThread thread = new MyThread();
thread.start();
实现 Runnable 接口:
public class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable is running...");
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
使用 Callable 和 Future(用于获取线程的执行结果):
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<String> {
public String call() {
return "Callable result";
}
}
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get(); // 阻塞直到结果返回
线程的生命周期包括以下几个状态:
start()
方法。生命周期状态转换示意图:
+-----------+ start() +----------+
| New | ----------> | Runnable |
+-----------+ +----------+
|
| 运行中
v
+--------------+
| Running |
+--------------+
|
sleep(), wait() |
-----------------> v
| +-----------+
| | Blocked |
|<-------------------+-----------+
|
v
+-----------+
| Terminated|
+-----------+
Java 提供了线程优先级机制,允许开发者为线程设置不同的优先级(从 1 到 10)。线程优先级越高,被调度的机会越大,但并不保证高优先级线程一定先执行。
Thread thread = new Thread(() -> System.out.println("Running..."));
thread.setPriority(Thread.MAX_PRIORITY); // 设置优先级为10
thread.start();
需要注意的是,线程优先级的实现依赖于底层操作系统,具体效果可能因平台而异。
线程同步是为了确保多线程访问共享资源时,不会导致数据的不一致。Java 提供了 synchronized
关键字,用于方法或代码块的同步。
public synchronized void increment() {
counter++;
}
public void increment() {
synchronized(this) {
counter++;
}
}
volatile
关键字用于修饰变量,保证对该变量的读写操作是直接从主内存中进行的,而不是使用线程的工作内存缓存。它适用于简单的状态标志或计数器,但不能替代锁。
private volatile boolean flag = true;
public void stop() {
flag = false;
}
wait()
、notify()
和 notifyAll()
是用于线程间通信的低级机制,常与 synchronized
一起使用。它们用于协调线程间的工作。
notify()
或 notifyAll()
。public synchronized void produce() throws InterruptedException {
while (isFull()) {
wait();
}
// 生产
notify();
}
public synchronized void consume() throws InterruptedException {
while (isEmpty()) {
wait();
}
// 消费
notify();
}
线程间的通信可以通过共享对象的状态或使用更高级的
并发工具类(如 BlockingQueue
)实现。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class Producer implements Runnable {
private BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
public void run() {
try {
for (int i = 0; i < 10; i++) {
queue.put(i);
System.out.println("Produced: " + i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
public void run() {
try {
for (int i = 0; i < 10; i++) {
int value = queue.take();
System.out.println("Consumed: " + value);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
Executor
框架提供了一种标准化的方式来管理和执行线程。它提供了灵活的线程管理机制,避免了手动创建和管理线程的复杂性。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(() -> System.out.println("Task executed"));
executorService.shutdown();
线程池是一种重用线程资源的机制,可以显著提高多线程应用的性能,减少资源开销。Java 的 ExecutorService
提供了多种线程池实现,如 FixedThreadPool
、CachedThreadPool
和 ScheduledThreadPool
。
CountDownLatch
是一种同步辅助类,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
import java.util.concurrent.CountDownLatch;
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " finished");
latch.countDown();
}).start();
}
latch.await(); // 主线程等待所有子线程完成
System.out.println("All tasks completed");
CyclicBarrier
是一个同步辅助类,允许一组线程互相等待,直到到达一个公共的屏障点。
import java.util.concurrent.CyclicBarrier;
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All parties have arrived"));
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " is waiting");
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
Semaphore
是一个计数信号量,控制访问某个资源的线程数目。
import java.util.concurrent.Semaphore;
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " is accessing");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
Java 提供了一些线程安全的集合类,如 ConcurrentHashMap
、CopyOnWriteArrayList
和 BlockingQueue
,它们简化了多线程环境下的数据结构操作。
ReentrantLock
是一种可重入的锁,比 synchronized
更加灵活。Condition
用于实现线程间的等待通知机制,比 wait
和 notify
更加灵活。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class LockExample {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void method1() {
lock.lock();
try {
condition.await(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void method2() {
lock.lock();
try {
condition.signal(); // 通知
} finally {
lock.unlock();
}
}
}
死锁是指两个或多个线程互相等待对方持有的资源,从而导致程序无法继续执行。解决死锁问题的方法包括:
线程饥饿是指某些线程长时间得不到执行的机会,通常是由于其他线程频繁占用资源。可以通过调整线程优先级、合理分配资源来缓解线程饥饿问题。
线程安全指的是在多线程环境下,程序的执行结果与单线程环境下一致。确保线程安全的方法包括:
synchronized
、ReentrantLock
、Atomic
类等。上下文切换是指操作系统在多个线程之间切换时保存和恢复线程的执行状态。频繁的上下文切换会导致性能下降。优化方法包括:
线程池是管理和重用线程资源的重要工具。合理配置线程池的大小、任务队列的长度以及拒绝策略,能够显著提升程序的性能和稳定性。
尽量缩小同步块的范围,减少锁的持有时间,避免阻塞其他线程的执行。只对必要的代码段进行同步,减少对程序性能的影响。
嵌套锁容易导致死锁,尽量避免在持有一个锁的同时去获取另一个锁。如果必须使用嵌套锁,应该严格按照顺序获取锁。
在多线程环境中,使用 Java 提供的并发集合类(如 ConcurrentHashMap
、CopyOnWriteArrayList
)能够减少锁竞争,提高程序的并发性能。
volatile
适用于简单的状态标志或计数器,但不能保证原子性操作。对于复杂的并发需求,仍需使用同步机制或并发工具类。
Java 多线程编程是一门复杂的技术,理解其原理并掌握各种同步机制和并发工具类是编写高效多线程程序的关键。通过合理设计线程模型、正确使用同步工具,并遵循多线程编程的最佳实践,可以有效提高程序的性能和可靠性。同时,避免常见的并发问题(如死锁、线程饥饿)也是确保多线程程序稳定运行的重要方面。多线程编程不仅需要扎实的理论知识,还需要大量的实践经验,因此在开发中应不断积累、总结,以便更好地应对复杂的并发场景。