Java 多线程
Java - 多线程与并发
Section titled “Java - 多线程与并发”Java 本身就是一种多线程(multithreaded)编程语言,允许你开发多个任务同时运行的程序。多线程程序包含两个或更多部分(线程,thread),它们可以看起来同时执行,从而最佳地利用可用资源,特别是在多核处理器上。
多任务处理(Multitasking)与多线程处理(Multithreading): 多任务处理涉及在多个进程(process,独立的程序)之间共享 CPU 资源。多线程处理将这个概念扩展到单个应用程序内部,将其操作细分为独立的线程,这些线程共享相同的内存空间。每个线程可以并行运行(在多个核心上)或并发运行(interleaved,在单个核心上交错执行)。
并发(Concurrency)允许编写响应迅速的应用程序,其中长时间运行的任务不会阻塞主执行流(例如,在执行后台计算时保持用户界面 UI 的响应)。
线程的生命周期(Life Cycle of a Thread):
Section titled “线程的生命周期(Life Cycle of a Thread):”线程在其生命周期内会经历几个状态:
- NEW(新建): 线程已被创建但尚未开始执行(即,其
start()方法尚未被调用)。 - RUNNABLE(可运行): 线程有资格由线程调度器运行。此状态包括实际在 CPU 上运行的线程和准备运行但等待调度器分配 CPU 时间的线程。
- BLOCKED(阻塞): 线程正在等待获取监视器锁(例如,进入一个目前被其他线程持有的
synchronized块或方法)。 - WAITING(等待): 线程无限期地等待另一个线程执行特定操作。这发生在调用
Object.wait()、Thread.join()或LockSupport.park()之后。 - TIMED_WAITING(有时限等待): 线程在指定的时间段内等待另一个线程执行操作或等待时间流逝。这发生在调用
Thread.sleep(long millis)、Object.wait(long timeout)、Thread.join(long millis)、LockSupport.parkNanos()或LockSupport.parkUntil()之后。 - TERMINATED(终止): 线程已完成执行(其
run()方法已结束)或以其他方式终止。
线程优先级(Thread Priorities):
Section titled “线程优先级(Thread Priorities):”每个 Java 线程都有一个优先级(一个介于 Thread.MIN_PRIORITY (1) 和 Thread.MAX_PRIORITY (10) 之间的整数,默认值为 Thread.NORM_PRIORITY (5))。
理论上,较高优先级的线程应该优先获得调度器的处理。然而,线程优先级的实际行为高度依赖于底层操作系统和 JVM 实现。不建议依赖优先级来保证程序的正确性,这可能导致不可移植的行为或饥饿(starvation)等问题。请改为使用适当的同步机制(synchronization mechanisms)。
创建线程:传统方式
Section titled “创建线程:传统方式”有两种定义线程将要执行的代码的基本方法:
1. 实现 Runnable 接口(推荐)
Section titled “1. 实现 Runnable 接口(推荐)”这种方式通常更受青睐,因为它将任务(Runnable)与执行机制(Thread)分离开,并且允许你的类在需要时继承另一个类。
- 创建一个实现
java.lang.Runnable接口的类。 - 在该类中实现
public void run()方法。此方法包含线程将要执行的代码。 - 创建你的
Runnable类的一个实例。 - 创建
java.lang.Thread的一个实例,并将你的Runnable对象传递给其构造方法。 - 调用
Thread对象的start()方法。这会创建一个新的系统线程并在该新线程中调用你的Runnable对象的run()方法。
示例(实现 Runnable):
Section titled “示例(实现 Runnable):”import java.util.concurrent.TimeUnit;
class MyRunnableTask implements Runnable { private final String taskName;
public MyRunnableTask(String name) { this.taskName = name; System.out.println("Creating task: " + taskName); }
@Override public void run() { System.out.println("Running task: " + taskName); try { for (int i = 3; i > 0; i--) { System.out.println("Task " + taskName + ", Count: " + i); // Use TimeUnit for clearer sleep duration TimeUnit.MILLISECONDS.sleep(100); } } catch (InterruptedException e) { // Restore the interrupted status Thread.currentThread().interrupt(); System.out.println("Task " + taskName + " interrupted."); } System.out.println("Task " + taskName + " finished."); }}
public class RunnableTest { public static void main(String[] args) { System.out.println("Main thread started.");
MyRunnableTask task1 = new MyRunnableTask("Task-A"); Thread thread1 = new Thread(task1);
MyRunnableTask task2 = new MyRunnableTask("Task-B"); Thread thread2 = new Thread(task2);
// Start the threads thread1.start(); thread2.start();
System.out.println("Main thread finished starting tasks."); // Note: main thread might finish before task threads complete }}2. 继承 Thread 类
Section titled “2. 继承 Thread 类”这种方法灵活性较低,因为 Java 只支持单继承。
- 创建一个继承
java.lang.Thread的类。 - 在你的类中重写
public void run()方法。 - 创建你的自定义
Thread子类的一个实例。 - 调用该实例的
start()方法。这会执行与Runnable方式相同的操作。
示例(继承 Thread):
Section titled “示例(继承 Thread):”import java.util.concurrent.TimeUnit;
class MyThread extends Thread { public MyThread(String name) { super(name); // Pass name to Thread constructor System.out.println("Creating thread: " + getName()); }
@Override public void run() { System.out.println("Running thread: " + getName()); try { for (int i = 3; i > 0; i--) { System.out.println("Thread " + getName() + ", Count: " + i); TimeUnit.MILLISECONDS.sleep(100); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.out.println("Thread " + getName() + " interrupted."); } System.out.println("Thread " + getName() + " finished."); }}
public class ThreadExtensionTest { public static void main(String[] args) { System.out.println("Main thread started.");
MyThread threadA = new MyThread("Thread-A"); MyThread threadB = new MyThread("Thread-B");
threadA.start(); threadB.start();
System.out.println("Main thread finished starting threads."); }}现代并发:java.util.concurrent 包
Section titled “现代并发:java.util.concurrent 包”虽然直接管理 Thread 对象是基础,但现代 Java 开发大量依赖于 java.util.concurrent 包(Java 5 引入)提供的高级抽象。该包提供了更健壮、高效且易于使用的并发工具。
- 执行器框架(Executor Framework): 将任务提交与任务执行解耦。
ExecutorService是主要接口,通常使用Executors类中的工厂方法创建(例如,Executors.newFixedThreadPool()、Executors.newCachedThreadPool())。这是在大多数应用程序中管理线程的推荐方式。 Callable<V>和Future<V>:Callable类似于Runnable,但它可以返回结果并抛出受检异常(checked exceptions)。当提交给ExecutorService后,它会返回一个Future对象,代表异步计算的结果。- 并发集合(Concurrent Collections): 线程安全的集合实现,例如
ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue。 - 同步器(Synchronizers): 用于协调线程的工具,例如
Semaphore、CountDownLatch、CyclicBarrier、Phaser。 - 锁(Locks): 比内置的
synchronized关键字更灵活的锁定机制,例如ReentrantLock、ReadWriteLock。
使用 ExecutorService 的示例:
Section titled “使用 ExecutorService 的示例:”import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;
public class ExecutorServiceTest { public static void main(String[] args) { System.out.println("Main thread started.");
// Create a fixed-size thread pool ExecutorService executor = Executors.newFixedThreadPool(2);
// Submit Runnable tasks executor.submit(new MyRunnableTask("PoolTask-1")); executor.submit(new MyRunnableTask("PoolTask-2")); executor.submit(new MyRunnableTask("PoolTask-3")); // Will wait if pool is busy
// Initiate graceful shutdown executor.shutdown(); // Stops accepting new tasks, waits for existing ones try { // Wait for tasks to complete for a maximum time if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { System.err.println("Tasks did not complete in time, forcing shutdown."); executor.shutdownNow(); // Attempt to interrupt running tasks } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); }
System.out.println("Main thread finished."); }
// Reusing MyRunnableTask from the previous example static class MyRunnableTask implements Runnable { // ... (same implementation as before) ... private final String taskName;
public MyRunnableTask(String name) { this.taskName = name; System.out.println("Creating task: " + taskName); }
@Override public void run() { System.out.println(Thread.currentThread().getName() + " running task: " + taskName); try { for (int i = 3; i > 0; i--) { System.out.println("Task " + taskName + ", Count: " + i); TimeUnit.MILLISECONDS.sleep(100); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.out.println("Task " + taskName + " interrupted."); } System.out.println(Thread.currentThread().getName() + " finished task: " + taskName); } }}重要的 Thread 方法:
Section titled “重要的 Thread 方法:”Thread 类中提供的关键方法:
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | public void start() | 通过在新线程上下文(context)中调用其 run() 方法来启动线程的执行。 |
| 2 | public void run() | 线程执行逻辑的入口点。直接调用 run() 会在当前线程中执行代码,而不是新线程。 |
| 3 | public final void join()public final void join(long millis) | 等待此线程终止。调用线程会阻塞,直到目标线程死亡或超时发生。 |
| 4 | public void interrupt() | 中断此线程。设置线程的中断状态标志。如果线程在 sleep()、wait() 或 join() 中阻塞,则会抛出 InterruptedException。 |
| 5 | public static boolean interrupted() | 检查当前线程是否已被中断并清除中断状态。 |
| 6 | public boolean isInterrupted() | 检查此线程是否已被中断,但不清除状态。 |
| 7 | public final boolean isAlive() | 如果线程已启动且尚未死亡,则返回 true。 |
| 8 | public static void sleep(long millis) | 使当前线程暂停执行指定的毫秒数(受调度器精度影响)。抛出 InterruptedException。 |
| 9 | public static Thread currentThread() | 返回对当前正在执行的线程对象的引用。 |
| 10 | public final void setDaemon(boolean on) | 将此线程标记为守护线程(daemon thread)。当只剩下守护线程运行时,JVM 会退出。 |
同步与线程安全
Section titled “同步与线程安全”当多个线程访问共享的可变数据时,你需要机制来防止竞态条件(race conditions)并确保数据一致性。这就是所谓的线程安全(thread safety)。
关键概念包括:
- 同步(Synchronization): 使用诸如
synchronized关键字或java.util.concurrent.locks.Lock实现之类的机制,以确保在同一时间只有一个线程可以访问代码的关键部分或共享资源。 - 原子性(Atomicity): 确保一个操作要么完全完成,要么完全不执行,不会受到其他线程的干扰(例如,使用
java.util.concurrent.atomic中的类)。 - 可见性(Visibility): 确保一个线程对共享变量所做的更改对其他线程可见(使用
volatile关键字或同步)。 - 死锁(Deadlock): 两个或更多线程都被永久阻塞的情况,每个线程都在等待对方释放资源。
- 不可变对象(Immutable Objects): 创建后状态无法更改的对象本质上是线程安全的。
开发正确的并发应用程序需要仔细的设计和对这些概念的理解。与使用 synchronized 进行手动锁定相比,使用 java.util.concurrent 中的工具通常能更简单地实现线程安全。