第2章

🧵 线程基础操作

掌握Java中创建线程的多种方式、线程属性设置以及异常处理机制

学习目标

继承Thread类

继承Thread类是创建线程最直接的方式。通过重写run()方法来定义线程要执行的任务,然后调用start()方法启动线程。

核心概念

run()方法定义线程要执行的任务,start()方法启动线程。直接调用run()方法不会创建新线程,而是在当前线程中执行。

基本用法

ThreadExample.java
public class ThreadExample extends Thread {
    private String threadName;
    
    public ThreadExample(String name) {
        this.threadName = name;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(threadName + " 执行第 " + (i + 1) + " 次");
            try {
                Thread.sleep(1000); // 暂停1秒
            } catch (InterruptedException e) {
                System.out.println(threadName + " 被中断");
                return;
            }
        }
        System.out.println(threadName + " 执行完成");
    }
    
    public static void main(String[] args) {
        // 创建线程对象
        ThreadExample thread1 = new ThreadExample("线程1");
        ThreadExample thread2 = new ThreadExample("线程2");
        
        // 启动线程
        thread1.start();
        thread2.start();
        
        System.out.println("主线程执行完成");
    }
}

run()和start()的区别

start()方法
  • 创建新的线程
  • 在新线程中执行run()方法
  • 只能调用一次
  • 异步执行
run()方法
  • 不创建新线程
  • 在当前线程中执行
  • 可以多次调用
  • 同步执行

实现Runnable接口

实现Runnable接口是创建线程的推荐方式。这种方式避免了Java单继承的限制,使得类可以继承其他类的同时实现多线程功能。

基本实现

RunnableExample.java
public class RunnableExample implements Runnable {
    private String taskName;
    
    public RunnableExample(String name) {
        this.taskName = name;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(taskName + " 执行第 " + (i + 1) + " 次");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(taskName + " 被中断");
                return;
            }
        }
        System.out.println(taskName + " 执行完成");
    }
    
    public static void main(String[] args) {
        // 创建Runnable对象
        RunnableExample task1 = new RunnableExample("任务1");
        RunnableExample task2 = new RunnableExample("任务2");
        
        // 创建Thread对象并启动
        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);
        
        thread1.start();
        thread2.start();
        
        System.out.println("主线程执行完成");
    }
}

Lambda表达式创建线程

LambdaThreadExample.java
public class LambdaThreadExample {
    public static void main(String[] args) {
        // 使用Lambda表达式创建线程
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Lambda线程执行第 " + (i + 1) + " 次");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("Lambda线程被中断");
                    return;
                }
            }
        });
        
        // 使用方法引用
        Thread thread2 = new Thread(LambdaThreadExample::printNumbers);
        
        thread1.start();
        thread2.start();
    }
    
    private static void printNumbers() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("方法引用线程: " + i);
            try {
                Thread.sleep(800);
            } catch (InterruptedException e) {
                System.out.println("方法引用线程被中断");
                return;
            }
        }
    }
}
Runnable接口的优势
  • 避免单继承限制:类可以继承其他类的同时实现Runnable
  • 代码复用:同一个Runnable对象可以被多个线程使用
  • 职责分离:任务逻辑与线程管理分离
  • 更好的设计:符合面向对象设计原则

使用Callable接口

Callable接口与Runnable类似,但它可以返回结果并抛出异常。通常与Future接口配合使用,可以获取线程执行的结果。

基本用法

CallableExample.java
import java.util.concurrent.*;

public class CallableExample {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        // 创建Callable任务
        Callable task1 = () -> {
            int sum = 0;
            for (int i = 1; i <= 100; i++) {
                sum += i;
                Thread.sleep(10); // 模拟计算时间
            }
            return sum;
        };
        
        Callable task2 = () -> {
            Thread.sleep(2000);
            return "Hello from Callable!";
        };
        
        try {
            // 提交任务并获取Future对象
            Future future1 = executor.submit(task1);
            Future future2 = executor.submit(task2);
            
            // 获取结果(会阻塞直到任务完成)
            System.out.println("计算结果: " + future1.get());
            System.out.println("字符串结果: " + future2.get());
            
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}

Future接口的方法

get()方法
获取任务执行结果,会阻塞直到任务完成。可以设置超时时间。
cancel()方法
取消任务执行。如果任务已经开始执行,可以选择是否中断。
isDone()方法
检查任务是否已经完成(正常完成、异常或取消)。

线程属性设置

线程具有多种属性,如名称、优先级、守护线程状态等。合理设置这些属性可以帮助我们更好地管理和调试线程。

线程属性示例

ThreadPropertiesExample.java
public class ThreadPropertiesExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + 
                    " 优先级: " + Thread.currentThread().getPriority() + 
                    " 是否守护线程: " + Thread.currentThread().isDaemon());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("线程被中断");
                    return;
                }
            }
        });
        
        // 设置线程名称
        thread.setName("自定义线程");
        
        // 设置线程优先级(1-10,默认5)
        thread.setPriority(Thread.MAX_PRIORITY);
        
        // 设置为守护线程(可选)
        thread.setDaemon(true);
        
        // 启动线程
        thread.start();
        
        // 主线程信息
        System.out.println("主线程名称: " + Thread.currentThread().getName());
        System.out.println("主线程优先级: " + Thread.currentThread().getPriority());
        
        try {
            Thread.sleep(5000); // 主线程等待5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("主线程结束");
    }
}

守护线程详解

守护线程特点
  • 后台服务:为其他线程提供服务的后台线程
  • 自动结束:当所有非守护线程结束时,守护线程自动结束
  • JVM退出:JVM不会等待守护线程执行完成
  • 典型应用:垃圾回收器、定时器等
DaemonThreadExample.java
public class DaemonThreadExample {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
            while (true) {
                System.out.println("守护线程正在运行...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("守护线程被中断");
                    return;
                }
            }
        });
        
        // 设置为守护线程
        daemonThread.setDaemon(true);
        daemonThread.start();
        
        // 主线程执行一段时间后结束
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("主线程结束,守护线程也会自动结束");
    }
}

线程异常处理

线程中的异常不能被主线程捕获,需要使用特殊的异常处理机制。Java提供了UncaughtExceptionHandler接口来处理线程中的未捕获异常。

异常处理示例

ThreadExceptionExample.java
public class ThreadExceptionExample {
    public static void main(String[] args) {
        // 设置全局异常处理器
        Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> {
            System.err.println("线程 " + thread.getName() + " 发生异常: " + exception.getMessage());
            exception.printStackTrace();
        });
        
        Thread thread1 = new Thread(() -> {
            System.out.println("线程1开始执行");
            // 故意制造异常
            int result = 10 / 0;
        }, "异常线程1");
        
        Thread thread2 = new Thread(() -> {
            System.out.println("线程2开始执行");
            try {
                Thread.sleep(2000);
                System.out.println("线程2正常结束");
            } catch (InterruptedException e) {
                System.out.println("线程2被中断");
            }
        }, "正常线程2");
        
        // 为特定线程设置异常处理器
        thread1.setUncaughtExceptionHandler((thread, exception) -> {
            System.err.println("特定处理器: 线程 " + thread.getName() + " 异常: " + exception.getClass().getSimpleName());
        });
        
        thread1.start();
        thread2.start();
        
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("主线程结束");
    }
}

异常处理最佳实践

预防异常
  • 在线程代码中使用try-catch
  • 验证输入参数
  • 处理可能的空指针
异常记录
  • 使用日志框架记录异常
  • 包含线程名称和时间戳
  • 记录异常堆栈信息
异常恢复
  • 实现重试机制
  • 优雅降级处理
  • 通知监控系统
上一章:多线程概述 返回目录 下一章:线程生命周期