第一部分:线程创建方式

Java提供了多种创建线程的方式,适用于不同场景需求

学习目标

掌握4种线程创建方式:继承Thread类、实现Runnable接口、实现Callable接口、使用线程池

1

继承Thread类

通过继承Thread类并重写run()方法创建线程

public class MyThread extends Thread { @Override public void run() { // 线程执行的代码 System.out.println("线程运行中: " + this.getName()); } public static void main(String[] args) { MyThread t1 = new MyThread(); t1.start(); // 启动线程 } }

注意:直接调用run()方法不会启动新线程,只会执行普通方法

2

实现Runnable接口

更常用的方式,避免单继承限制

public class MyRunnable implements Runnable { @Override public void run() { System.out.println("Runnable线程运行中: " + Thread.currentThread().getName()); } public static void main(String[] args) { Thread t = new Thread(new MyRunnable()); t.start(); } }
3

实现Callable接口

带返回值的线程创建方式,需要配合FutureTask使用

import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class MyCallable implements Callable<String> { @Override public String call() throws Exception { // 执行任务并返回结果 return "Callable执行结果: " + Thread.currentThread().getName(); } public static void main(String[] args) throws Exception { FutureTask<String> futureTask = new FutureTask<>(new MyCallable()); Thread t = new Thread(futureTask); t.start(); // 获取执行结果(阻塞直到线程完成) String result = futureTask.get(); System.out.println(result); } }
4

使用线程池

推荐的生产环境用法,高效管理线程资源

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { // 创建固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(3); // 提交任务到线程池 for (int i = 0; i < 5; i++) { executor.execute(() -> { System.out.println("线程池执行: " + Thread.currentThread().getName()); }); } // 关闭线程池 executor.shutdown(); } }

创建方式对比

方式 优点 缺点 适用场景
继承Thread 简单直接 无法继承其他类 简单任务
实现Runnable 解耦任务与线程,可继承其他类 无返回值 大多数场景
实现Callable 支持返回值,可抛出异常 使用复杂 需要返回结果
线程池 高效资源管理,避免频繁创建销毁 配置复杂 生产环境

最佳实践建议

1. 优先选择实现Runnable接口方式创建线程,避免单继承限制
2. 需要返回值时使用Callable接口
3. 生产环境必须使用线程池管理线程资源
4. 避免直接创建Thread对象,防止资源耗尽

第二部分:线程生命周期

理解线程的6种状态及其转换关系

学习目标

掌握线程的6种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

NEW
新建
RUNNABLE
可运行
BLOCKED
阻塞
WAITING
等待
TIMED_WAITING
限时等待
TERMINATED
终止

NEW(新建)

线程被创建但未启动时的状态

创建方式: new Thread()

转换条件: 调用start()方法进入RUNNABLE状态

RUNNABLE(可运行)

线程正在运行或准备运行的状态

包含: 就绪(Ready)和运行中(Running)

转换条件: 时间片用完或主动放弃进入BLOCKED/WAITING

BLOCKED(阻塞)

等待获取锁时的状态

触发条件: 进入synchronized方法/块

转换条件: 获取到锁后进入RUNNABLE状态

WAITING(等待)

无限期等待其他线程操作

触发方法: Object.wait(), Thread.join()

转换条件: 被notify()/notifyAll()唤醒

TIMED_WAITING(限时等待)

有限时间等待状态

触发方法: Thread.sleep(), Object.wait(timeout)

转换条件: 时间结束或被唤醒

TERMINATED(终止)

线程执行结束后的状态

触发条件: run()方法执行完成

转换条件: 不可再启动

状态监控技巧

1. 使用jstack工具查看线程状态
2. 在IDEA中通过调试视图查看线程状态
3. Thread.getState()方法获取状态
4. 避免线程长时间处于BLOCKED/WAITING状态

第三部分:线程常用方法

掌握线程控制的核心方法

学习目标

理解并掌握start(), run(), sleep(), join(), yield(), interrupt()等方法的使用

方法 描述 使用示例 注意事项
start() 启动线程,进入RUNNABLE状态 thread.start() 只能调用一次
run() 线程执行的主体方法 重写此方法 不要直接调用
sleep(long millis) 使线程休眠指定毫秒数 Thread.sleep(1000) 不释放锁
join() 等待线程执行完毕 thread.join() 可能导致死锁
yield() 让出CPU时间片 Thread.yield() 不保证立即切换
interrupt() 中断线程 thread.interrupt() 需要配合异常处理

interrupt()方法详解

中断线程的正确方式:

public class InterruptExample implements Runnable { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { // 模拟工作 Thread.sleep(1000); System.out.println("工作线程运行中..."); } catch (InterruptedException e) { // 捕获中断异常 System.out.println("线程被中断!"); // 恢复中断状态 Thread.currentThread().interrupt(); } } System.out.println("线程结束"); } public static void main(String[] args) throws Exception { Thread t = new Thread(new InterruptExample()); t.start(); // 主线程等待3秒后中断工作线程 Thread.sleep(3000); t.interrupt(); } }
工作线程运行中...
工作线程运行中...
线程被中断!
线程结束

重要注意事项

1. 不要使用stop()、suspend()等已废弃方法
2. 正确处理InterruptedException
3. volatile不能替代synchronized
4. sleep()和wait()的区别:sleep()不释放锁,wait()释放锁

第四部分:线程同步与锁

解决多线程并发访问共享资源的问题

学习目标

掌握synchronized关键字和Lock接口的使用,理解死锁产生原因及预防

synchronized关键字

Java内置同步机制,简单易用

public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; } // 或使用同步块 public void increment2() { synchronized(this) { count++; } } }

优点: 自动释放锁,无需手动管理

缺点: 功能有限,无法中断等待锁的线程

Lock接口

更灵活的同步控制,需要手动释放锁

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private final Lock lock = new ReentrantLock(); private int count = 0; public void increment() { lock.lock(); // 获取锁 try { count++; } finally { lock.unlock(); // 确保释放锁 } } }

优点: 可中断、可尝试获取、公平锁等

缺点: 需要手动释放,使用不当可能导致死锁

死锁示例与避免

public class DeadlockExample { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (lock1) { System.out.println("线程1获取lock1"); try { Thread.sleep(100); } catch (Exception e) {} synchronized (lock2) { System.out.println("线程1获取lock2"); } } }).start(); new Thread(() -> { synchronized (lock2) { System.out.println("线程2获取lock2"); try { Thread.sleep(100); } catch (Exception e) {} synchronized (lock1) { System.out.println("线程2获取lock1"); } } }).start(); } }

避免死锁策略

1. 按固定顺序获取锁资源
2. 使用tryLock()尝试获取锁
3. 设置超时时间
4. 使用死锁检测工具

线程1: 持有 lock1,等待 lock2
线程2: 持有 lock2,等待 lock1

死锁发生时两个线程互相等待对方释放资源

第五部分:常见错误与解决方案

多线程编程中的典型问题与调试技巧

1. 竞态条件(Race Condition)

现象: 多个线程同时修改共享数据,结果不确定

解决方案: 使用synchronized或Lock保证操作的原子性

2. 死锁(Deadlock)

现象: 多个线程互相等待对方释放资源,程序停滞

解决方案: 按顺序获取锁,设置超时,使用死锁检测工具

3. 活锁(Livelock)

现象: 线程不断响应对方动作,无法继续执行

解决方案: 引入随机性,使用重试退避策略

4. 线程饥饿(Starvation)

现象: 某些线程长时间无法获取资源

解决方案: 使用公平锁,调整线程优先级

5. 内存可见性问题

现象: 线程修改值后其他线程看不到更新

解决方案: 使用volatile关键字或synchronized保证可见性

6. 资源泄漏

现象: 线程未正确关闭,占用系统资源

解决方案: 使用try-finally确保资源释放,使用线程池管理

调试多线程程序的技巧

1. 使用Thread Dump分析线程状态
2. 利用日志记录线程ID和操作
3. 使用IDE的并发调试工具
4. 编写确定性测试用例
5. 限制并发数逐步排查问题

第六部分:练习与测验

通过练习巩固线程知识

动手练习

  • 实现一个生产者-消费者模型
  • 使用Callable和Future实现并行计算
  • 编写一个死锁示例并解决它
  • 使用线程池实现Web服务器请求处理
1

线程知识测验

以下哪种方式可以正确启动一个线程?
synchronized关键字的作用是什么?

学习资源推荐

1. 《Java并发编程实战》- Brian Goetz
2. 《Java并发编程之美》- 翟陆续
3. Oracle官方Java并发教程
4. Java并发工具包(java.util.concurrent)文档