Java多线程全面解析
1. 多线程基础概念
1.1 进程与线程
进程是操作系统进行资源分配和调度的基本单位,每个进程都有独立的内存空间和系统资源。一个进程崩溃不会影响其他进程的运行,提供了良好的隔离性。
线程是进程中的一个执行单元,是CPU调度和执行的最小单位。同一进程内的所有线程共享进程的内存空间和系统资源,这使得线程间的通信更加高效,但也带来了数据同步的挑战。
1.2 为什么需要多线程
- 提高程序响应性:对于图形界面应用程序,使用多线程可以避免用户界面”冻结”
- 提高CPU利用率:当某个线程等待I/O操作时,其他线程可以继续执行,充分利用CPU资源
- 简化程序结构:对于需要处理多个独立任务的程序,多线程可以提供更清晰的设计
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(); } }
|
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) { MyRunnable runnable = new MyRunnable(); 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) { 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); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println("新建状态: " + thread.getState()); thread.start(); System.out.println("启动后状态: " + thread.getState()); Thread.sleep(100); System.out.println("睡眠中状态: " + thread.getState()); thread.join(); System.out.println("结束后状态: " + thread.getState()); } }
|
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更灵活的锁操作:
- 可重入性:同一个线程可以多次获取同一把锁
- 可中断:等待锁的线程可以被中断
- 公平性:可以选择公平锁或非公平锁
- 条件变量:可以创建多个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 为什么使用线程池
- 降低资源消耗:减少创建和销毁线程的开销
- 提高响应速度:任务到达时可以直接使用已有线程
- 提高线程可管理性:可以统一分配、调优和监控线程
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的构造函数包含多个重要参数:
- corePoolSize:核心线程数,即使线程空闲也不会被回收
- maximumPoolSize:最大线程数,线程池中允许的最大线程数量
- keepAliveTime:空闲线程存活时间
- unit:keepAliveTime的时间单位
- workQueue:工作队列,用于保存等待执行的任务
- threadFactory:线程工厂,用于创建新线程
- 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(); }).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) { Semaphore semaphore = new Semaphore(3); for (int i = 0; i < 6; i++) { new Thread(() -> { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " 抢到车位"); Thread.sleep(2000); 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()); } }
|
总结
Java多线程编程是一个复杂但重要的主题。掌握多线程技术可以帮助开发出高性能、高并发的应用程序。在实际开发中,应该:
- 优先使用高级并发工具类,而不是直接使用底层同步机制
- 合理使用线程池,避免频繁创建和销毁线程
- 注意线程安全问题,适当使用同步机制保护共享资源
- 避免死锁,确保锁的获取和释放顺序一致
- 使用合适的并发工具解决特定的并发问题