多线程总结
Java中的多线程是Java编程语言中的一个重要特性,它允许多个线程同时执行,从而提高程序的性能和响应能力。多线程在处理并发任务、优化资源利用率以及构建高性能应用程序方面发挥着关键作用。本文将详细介绍Java中的多线程,包括其基本概念、线程的创建与管理、线程同步、并发工具类以及最佳实践等内容。
一、多线程基础
1. 什么是线程
线程是操作系统能够进行运算调度的最小单位,它是比进程更小的独立运行的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源(如内存空间),但每个线程有自己独立的执行路径。
2. 多线程的优势
- 提高程序性能:通过并行执行多个任务,充分利用多核处理器的能力。
- 增强应用响应性:在图形用户界面(GUI)应用中,使用多线程可以防止界面冻结,提高用户体验。
- 更好的资源利用:通过合理调度线程,可以更有效地使用系统资源,如CPU和内存。
3. 多线程的挑战
- 线程安全:多个线程访问共享资源时,必须确保数据的一致性和完整性。
- 死锁和竞态条件:不当的线程同步可能导致死锁或竞态条件,影响程序的稳定性。
- 调试和维护复杂性:多线程程序的调试和维护比单线程程序更为复杂。
二、线程的创建与管理
Java提供了多种创建线程的方式,主要包括继承Thread
类、实现Runnable
接口以及使用Callable
和Future
接口。
1. 继承Thread
类
通过继承Thread
类并重写run()
方法来定义线程的任务。
示例:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在运行");
}
}
public class ThreadExample1 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.setName("线程1");
t1.start();
MyThread t2 = new MyThread();
t2.setName("线程2");
t2.start();
}
}
输出:
线程 线程1 正在运行
线程 线程2 正在运行
2. 实现Runnable
接口
通过实现Runnable
接口并将其实例传递给Thread
对象来创建线程。这种方式避免了单继承的限制,更加灵活。
示例:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在运行");
}
}
public class ThreadExample2 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable(), "线程1");
Thread t2 = new Thread(new MyRunnable(), "线程2");
t1.start();
t2.start();
}
}
3. 使用Callable
和Future
Callable
接口类似于Runnable
,但它可以返回结果并抛出异常。Future
接口用于获取异步计算的结果。
示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable线程执行结果";
}
}
public class ThreadExample3 {
public static void main(String[] args) {
Callable<String> callable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask, "Callable线程");
t.start();
try {
String result = futureTask.get(); // 获取返回结果
System.out.println("返回结果: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
输出:
返回结果: Callable线程执行结果
4. 使用线程池
线程池通过复用线程来提高性能,避免频繁创建和销毁线程的开销。Java的java.util.concurrent
包提供了强大的线程池实现。
示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("任务 " + name + " 在 " + Thread.currentThread().getName() + " 执行");
}
}
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
executor.execute(new Task("Task-" + i));
}
executor.shutdown(); // 关闭线程池
}
}
输出:
任务 Task-1 在 pool-1-thread-1 执行
任务 Task-2 在 pool-1-thread-2 执行
任务 Task-3 在 pool-1-thread-3 执行
任务 Task-4 在 pool-1-thread-1 执行
任务 Task-5 在 pool-1-thread-2 执行
三、线程生命周期
线程在其生命周期中会经历多个状态:
- 新建(New):线程对象被创建,但尚未调用
start()
方法。 - 就绪(Runnable):线程已准备好运行,等待CPU调度。
- 运行(Running):线程正在执行其任务。
- 阻塞(Blocked/Waiting):线程因等待某个条件而暂停执行,如等待锁或等待某个事件。
- 终止(Terminated):线程的
run()
方法执行完毕或因异常终止。
1. 线程状态示例
示例:
public class ThreadLifecycleExample {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("线程状态: " + Thread.currentThread().getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "测试线程");
System.out.println("线程状态: " + t.getState()); // NEW
t.start();
System.out.println("线程状态: " + t.getState()); // RUNNABLE
try {
t.join();
System.out.println("线程状态: " + t.getState()); // TERMINATED
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
线程状态: NEW
线程状态: RUNNABLE
线程状态: TERMINATED
四、线程同步
在多线程环境中,多个线程可能会同时访问共享资源,导致数据不一致。为确保线程安全,需要对共享资源进行同步控制。
1. synchronized
关键字
synchronized
关键字可以用于方法或代码块,确保同一时刻只有一个线程可以执行被同步的代码。
示例:
class Counter {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数值: " + counter.getCount()); // 应为2000
}
}
2. 同步代码块
除了同步整个方法,还可以同步方法中的某个代码块,以提高并发性能。
示例:
class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized(lock) {
count++;
}
}
public int getCount() {
return count;
}
}
3. volatile
关键字
volatile
关键字用于声明变量在多个线程间的可见性,确保每个线程都读取到最新的变量值。但它不能保证原子性。
示例:
class Flag {
private volatile boolean running = true;
public void stop() {
running = false;
}
public boolean isRunning() {
return running;
}
}
public class VolatileExample {
public static void main(String[] args) throws InterruptedException {
Flag flag = new Flag();
Thread t = new Thread(() -> {
while (flag.isRunning()) {
// 执行任务
}
System.out.println("线程已停止");
});
t.start();
Thread.sleep(1000);
flag.stop();
}
}
4. ReentrantLock
锁
ReentrantLock
是java.util.concurrent.locks
包中的一个灵活的锁实现,提供了比synchronized
更高级的功能,如可中断锁获取、公平锁等。
示例:
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
public class ReentrantLockExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for(int i=0; i<1000; i++) {
counter.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数值: " + counter.getCount()); // 应为2000
}
}
五、Java并发工具类
Java的java.util.concurrent
包提供了一系列并发工具类,简化了多线程编程中的同步和协调工作。
1. CountDownLatch
CountDownLatch
用于让一个或多个线程等待,直到其他线程完成一组操作。
示例:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int numWorkers = 3;
CountDownLatch latch = new CountDownLatch(numWorkers);
for(int i=1; i<=numWorkers; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始工作");
try {
Thread.sleep((long)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 完成工作");
latch.countDown();
}, "Worker-" + i).start();
}
latch.await(); // 等待所有工作线程完成
System.out.println("所有工作线程已完成,继续执行主线程");
}
}
2. Semaphore
Semaphore
用于控制同时访问某个资源的线程数量,常用于限流和资源池管理。
示例:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 同时允许3个线程访问
for(int i=1; i<=5; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获取许可证,正在执行任务");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " 释放许可证");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Thread-" + i).start();
}
}
}
3. CyclicBarrier
CyclicBarrier
用于让一组线程在某个点上等待,直到所有线程都到达该点,然后同时继续执行。
示例:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int numParties = 3;
CyclicBarrier barrier = new CyclicBarrier(numParties, () -> {
System.out.println("所有线程已到达屏障,继续执行");
});
for(int i=1; i<=numParties; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 正在执行任务");
Thread.sleep((long)(Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 已到达屏障");
barrier.await();
System.out.println(Thread.currentThread().getName() + " 继续执行任务");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, "Thread-" + i).start();
}
}
}
4. BlockingQueue
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;
}
@Override
public void run() {
try {
for(int i=1; i<=5; i++) {
System.out.println("生产者生产: " + i);
queue.put(i);
Thread.sleep(500);
}
queue.put(-1); // 结束标志
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumer implements Runnable {
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while(true) {
int value = queue.take();
if(value == -1) break;
System.out.println("消费者消费: " + value);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
Thread producer = new Thread(new Producer(queue), "Producer");
Thread consumer = new Thread(new Consumer(queue), "Consumer");
producer.start();
consumer.start();
}
}
输出:
生产者生产: 1
消费者消费: 1
生产者生产: 2
消费者消费: 2
生产者生产: 3
消费者消费: 3
生产者生产: 4
消费者消费: 4
生产者生产: 5
消费者消费: 5
六、线程安全与最佳实践
1. 避免共享可变状态
尽量减少线程间共享的可变状态,使用不可变对象(如String
、Integer
)可以提高线程安全性。
2. 使用并发集合
Java提供了线程安全的集合类,如ConcurrentHashMap
、CopyOnWriteArrayList
等,避免手动同步。
示例:使用ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
Runnable task = () -> {
for(int i=0; i<1000; i++) {
map.merge("key", 1, Integer::sum);
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终值: " + map.get("key")); // 应为2000
}
}
3. 避免死锁
- 有序获取锁:确保多个线程以相同的顺序获取锁,避免循环等待。
- 锁超时:使用带超时的锁获取方法,如
tryLock(long timeout, TimeUnit unit)
,避免无限等待。
示例:使用tryLock
避免死锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class DeadlockAvoidanceExample {
private Lock lock1 = new ReentrantLock();
private Lock lock2 = new ReentrantLock();
public void methodA() {
try {
if(lock1.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " 获取了 lock1");
Thread.sleep(500);
if(lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread().getName() + " 获取了 lock2");
// 执行任务
} finally {
lock2.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
}
}
public void methodB() {
try {
if(lock2.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " 获取了 lock2");
Thread.sleep(500);
if(lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread().getName() + " 获取了 lock1");
// 执行任务
} finally {
lock1.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
}
}
public static void main(String[] args) {
DeadlockAvoidanceExample example = new DeadlockAvoidanceExample();
Thread t1 = new Thread(() -> {
example.methodA();
}, "Thread-1");
Thread t2 = new Thread(() -> {
example.methodB();
}, "Thread-2");
t1.start();
t2.start();
}
}
4. 使用高层并发API
尽量使用Java提供的高层并发API,如ExecutorService
、ForkJoinPool
等,简化多线程编程并提高性能。
七、线程通信
线程之间有时需要协同工作,通过通信机制可以实现线程间的数据共享和同步。
1. wait()
和notify()
使用对象的wait()
、notify()
和notifyAll()
方法进行线程通信。这些方法必须在同步块或同步方法中使用。
示例:生产者-消费者模型
class SharedResource {
private int data;
private boolean available = false;
public synchronized void produce(int value) throws InterruptedException {
while(available) {
wait();
}
data = value;
available = true;
notifyAll();
}
public synchronized int consume() throws InterruptedException {
while(!available) {
wait();
}
available = false;
notifyAll();
return data;
}
}
public class WaitNotifyExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
// 生产者线程
Thread producer = new Thread(() -> {
for(int i=1; i<=5; i++) {
try {
resource.produce(i);
System.out.println("生产者生产: " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
for(int i=1; i<=5; i++) {
try {
int value = resource.consume();
System.out.println("消费者消费: " + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
}
输出:
生产者生产: 1
消费者消费: 1
生产者生产: 2
消费者消费: 2
生产者生产: 3
消费者消费: 3
生产者生产: 4
消费者消费: 4
生产者生产: 5
消费者消费: 5
2. BlockingQueue
替代wait()
和notify()
使用BlockingQueue
可以更简洁地实现生产者-消费者模型,无需手动管理wait()
和notify()
。
示例:
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;
}
@Override
public void run() {
try {
for(int i=1; i<=5; i++) {
System.out.println("生产者生产: " + i);
queue.put(i);
Thread.sleep(500);
}
queue.put(-1); // 结束标志
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumer implements Runnable {
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while(true) {
int value = queue.take();
if(value == -1) break;
System.out.println("消费者消费: " + value);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class BlockingQueueCommunicationExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
Thread producer = new Thread(new Producer(queue), "Producer");
Thread consumer = new Thread(new Consumer(queue), "Consumer");
producer.start();
consumer.start();
}
}
八、线程优先级与中断
1. 线程优先级
Java线程有优先级,范围从Thread.MIN_PRIORITY
(1)到Thread.MAX_PRIORITY
(10),默认为Thread.NORM_PRIORITY
(5)。优先级高的线程更有可能获得CPU时间,但优先级并不保证执行顺序。
示例:
public class ThreadPriorityExample {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("线程 " + Thread.currentThread().getName() + " 优先级: " + Thread.currentThread().getPriority());
};
Thread t1 = new Thread(task, "低优先级线程");
Thread t2 = new Thread(task, "高优先级线程");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
输出:
线程 低优先级线程 优先级: 1
线程 高优先级线程 优先级: 10
2. 线程中断
线程可以通过调用interrupt()
方法来请求中断,线程需要自行处理中断请求。
示例:
public class ThreadInterruptExample {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()) {
try {
System.out.println("线程运行中...");
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程被中断");
Thread.currentThread().interrupt(); // 重新设置中断标志
}
}
System.out.println("线程结束");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt(); // 请求中断线程
}
}
输出:
线程运行中...
线程运行中...
线程运行中...
线程被中断
线程结束
九、总结
Java的多线程机制为开发高性能和高响应性的应用程序提供了强大的支持。通过理解和掌握线程的创建、管理、同步和通信方法,开发者可以有效地利用多核处理器的能力,优化程序性能。然而,多线程编程也带来了诸如线程安全、死锁和调试复杂性等挑战。因此,在实际开发中,应遵循最佳实践,合理使用Java提供的并发工具类,确保程序的健壮性和可维护性。
主要内容回顾
- 线程的创建与管理:通过继承
Thread
类、实现Runnable
或Callable
接口以及使用线程池等方式创建和管理线程。 - 线程同步:使用
synchronized
关键字、ReentrantLock
等机制确保线程安全,避免数据不一致。 - 并发工具类:利用
CountDownLatch
、Semaphore
、CyclicBarrier
、BlockingQueue
等工具类简化并发编程。 - 线程通信:通过
wait()
、notify()
以及BlockingQueue
等方式实现线程间的协调与通信。 - 线程优先级与中断:合理设置线程优先级和处理中断请求,提高程序的响应能力和稳定性。