Java多线程简单指南
·
目录
1. 什么是多线程?
多线程是Java编程中一个非常重要的概念,它允许程序同时执行多个任务,从而提高程序的执行效率和响应速度。想象一下,你在厨房做饭:一边煮水,一边切菜,一边炒菜——这就是多线程的日常生活例子。
1.1 线程与进程的区别
- 进程:操作系统分配资源的基本单位,每个进程都有独立的内存空间
- 线程:进程内的执行单元,多个线程共享进程的内存空间
- 简单说:一个进程可以有多个线程,线程是"轻量级"的进程
2. 为什么需要多线程?
2.1 多线程的优势
- 提高CPU利用率:当一个线程等待I/O操作时,CPU可以执行其他线程
- 改善响应性:GUI程序不会因为耗时操作而"卡死"
- 简化模型:某些任务天然适合并发执行(如Web服务器处理多个请求)
- 充分利用多核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 线程状态说明
- 新建(New):创建了Thread对象但未调用start()
- 就绪(Runnable):调用了start(),等待CPU分配时间片
- 运行(Running):获得CPU时间片,正在执行run()方法
- 阻塞(Blocked):等待某个条件(如I/O完成、锁释放)
- 终止(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 为什么使用线程池?
- 降低资源消耗:重复利用已创建的线程
- 提高响应速度:任务到达时无需等待线程创建
- 提高线程可管理性:统一分配、调优和监控
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 常见陷阱
- 竞态条件(Race Condition):多个线程同时修改共享数据
- 死锁(Deadlock):两个或多个线程互相等待对方释放锁
- 活锁(Livelock):线程不断改变状态但无法继续执行
- 内存可见性问题:一个线程的修改对其他线程不可见
- 线程饥饿(Starvation):低优先级线程永远得不到执行机会
10. 总结
Java多线程编程是Java开发者的必备技能。初学者应该:
- 从基础开始:先理解线程、进程的基本概念
- 掌握三种创建方式:Thread、Runnable、Callable
- 理解线程安全:学会使用synchronized和锁
- 使用线程池:实际开发中优先使用Executor框架
- 多练习多调试:通过实际案例加深理解
记住:多线程编程虽然强大,但也复杂。始终要考虑线程安全、性能平衡和代码可维护性。从简单案例开始,逐步深入,你一定能掌握这门重要的技术!
所有评论(0)