1. 什么是多线程?

多线程是Java编程中一个非常重要的概念,它允许程序同时执行多个任务,从而提高程序的执行效率和响应速度。想象一下,你在厨房做饭:一边煮水,一边切菜,一边炒菜——这就是多线程的日常生活例子。

1.1 线程与进程的区别

  • 进程:操作系统分配资源的基本单位,每个进程都有独立的内存空间
  • 线程:进程内的执行单元,多个线程共享进程的内存空间
  • 简单说:一个进程可以有多个线程,线程是"轻量级"的进程

2. 为什么需要多线程?

2.1 多线程的优势

  1. 提高CPU利用率:当一个线程等待I/O操作时,CPU可以执行其他线程
  2. 改善响应性:GUI程序不会因为耗时操作而"卡死"
  3. 简化模型:某些任务天然适合并发执行(如Web服务器处理多个请求)
  4. 充分利用多核CPU:现代CPU都是多核心的,多线程可以并行执行

2.2 适用场景

  • 文件下载(多个文件同时下载)
  • 数据处理(大数据分批处理)
  • 游戏开发(渲染、逻辑、音效并行)
  • Web服务器(同时处理多个客户端请求)

3. Java中创建线程的三种方式

3.1 继承Thread类(最简单的方式)

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程要执行的任务
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(500); // 暂停500毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 使用方式
public class Main {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        
        thread1.setName("线程A");
        thread2.setName("线程B");
        
        thread1.start(); // 启动线程
        thread2.start();
    }
}

3.2 实现Runnable接口(推荐方式)

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 使用方式
public class Main {
    public static void main(String[] args) {
        Runnable task = new MyRunnable();
        
        Thread thread1 = new Thread(task, "线程A");
        Thread thread2 = new Thread(task, "线程B");
        
        thread1.start();
        thread2.start();
    }
}

为什么推荐Runnable接口?

  • Java不支持多重继承,但可以实现多个接口
  • 更适合资源共享(多个线程可以共享同一个Runnable实例)
  • 符合面向接口编程的原则

3.3 实现Callable接口(可以返回结果)

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<Integer> {
    private int n;
    
    public MyCallable(int n) {
        this.n = n;
    }
    
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
            Thread.sleep(100);
        }
        return sum; // 可以返回结果
    }
}

4 线程状态说明

  1. 新建(New):创建了Thread对象但未调用start()
  2. 就绪(Runnable):调用了start(),等待CPU分配时间片
  3. 运行(Running):获得CPU时间片,正在执行run()方法
  4. 阻塞(Blocked):等待某个条件(如I/O完成、锁释放)
  5. 终止(Terminated):run()方法执行完毕或线程被中断

5. 线程常用方法

5.1 基本控制方法

public class ThreadMethodsDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 1; i <= 3; i++) {
                System.out.println("子线程执行: " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("线程被中断");
                    return;
                }
            }
        });
        
        // 启动线程
        thread.start();
        
        // 让主线程等待子线程执行完毕
        thread.join();
        
        System.out.println("主线程继续执行");
        
        // 获取当前线程
        Thread currentThread = Thread.currentThread();
        System.out.println("当前线程: " + currentThread.getName());
        System.out.println("线程ID: " + currentThread.getId());
        System.out.println("线程优先级: " + currentThread.getPriority());
        
        // 设置线程优先级(1-10,默认5)
        thread.setPriority(Thread.MAX_PRIORITY); // 10
    }
}

5.2 sleep() vs yield()

  • sleep(毫秒):让当前线程暂停指定时间,进入阻塞状态
  • yield():让出CPU时间片,回到就绪状态,让同优先级的其他线程有机会执行

6. 线程安全问题与同步

6.1 线程安全问题示例

public class UnsafeCounter {
    private int count = 0;
    
    public void increment() {
        count++; // 这不是原子操作!
    }
    
    public int getCount() {
        return count;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        UnsafeCounter counter = new UnsafeCounter();
        
        // 创建100个线程,每个线程增加1000次
        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }
        
        // 等待所有线程执行完毕
        for (Thread thread : threads) {
            thread.join();
        }
        
        // 理论上应该是100000,但实际上可能小于这个值
        System.out.println("最终计数: " + counter.getCount());
    }
}

6.2 使用synchronized解决线程安全问题

// 方法1:同步方法
public class SafeCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

// 方法2:同步代码块
public class SafeCounter2 {
    private int count = 0;
    private final Object lock = new Object(); // 专门的锁对象
    
    public void increment() {
        synchronized (lock) {
            count++;
        }
    }
    
    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

6.3 使用ReentrantLock(更灵活的锁)

import java.util.concurrent.locks.ReentrantLock;

public class LockCounter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++;
        } finally {
            lock.unlock(); // 确保锁被释放
        }
    }
    
    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

7. 线程间通信

7.1 wait()和notify()方法

public class ProducerConsumerExample {
    private final Object lock = new Object();
    private boolean hasData = false;
    private String data;
    
    // 生产者
    public void produce(String value) throws InterruptedException {
        synchronized (lock) {
            while (hasData) {
                lock.wait(); // 等待消费者消费
            }
            data = value;
            hasData = true;
            System.out.println("生产: " + value);
            lock.notify(); // 通知消费者
        }
    }
    
    // 消费者
    public String consume() throws InterruptedException {
        synchronized (lock) {
            while (!hasData) {
                lock.wait(); // 等待生产者生产
            }
            String value = data;
            hasData = false;
            System.out.println("消费: " + value);
            lock.notify(); // 通知生产者
            return value;
        }
    }
}

8. 线程池(ThreadPool) - 实际开发推荐

8.1 为什么使用线程池?

  1. 降低资源消耗:重复利用已创建的线程
  2. 提高响应速度:任务到达时无需等待线程创建
  3. 提高线程可管理性:统一分配、调优和监控

8.2 使用Executor框架

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建固定大小的线程池(推荐)
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 提交10个任务
        for (int i = 1; i <= 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                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();
        }
    }
}

8.3 不同类型的线程池

// 1. 固定大小线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);

// 2. 单线程线程池(保证任务顺序执行)
ExecutorService singlePool = Executors.newSingleThreadExecutor();

// 3. 缓存线程池(根据需要创建新线程)
ExecutorService cachedPool = Executors.newCachedThreadPool();

// 4. 定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);

9 常见陷阱

  1. 竞态条件(Race Condition):多个线程同时修改共享数据
  2. 死锁(Deadlock):两个或多个线程互相等待对方释放锁
  3. 活锁(Livelock):线程不断改变状态但无法继续执行
  4. 内存可见性问题:一个线程的修改对其他线程不可见
  5. 线程饥饿(Starvation):低优先级线程永远得不到执行机会

10. 总结

Java多线程编程是Java开发者的必备技能。初学者应该:

  1. 从基础开始:先理解线程、进程的基本概念
  2. 掌握三种创建方式:Thread、Runnable、Callable
  3. 理解线程安全:学会使用synchronized和锁
  4. 使用线程池:实际开发中优先使用Executor框架
  5. 多练习多调试:通过实际案例加深理解

记住:多线程编程虽然强大,但也复杂。始终要考虑线程安全、性能平衡和代码可维护性。从简单案例开始,逐步深入,你一定能掌握这门重要的技术!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。