Java多线程基础详解:从实现到线程安全
一、Thread与Runnable实现方式对比
1.1 继承Thread类实现多线程
class MyThread extends Thread {@Overridepublic void run() {System.out.println("Thread running: " + Thread.currentThread().getName());}
}public class ThreadDemo {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.start();t2.start();}
}
特点:
- 直接继承Thread类并重写run()
- 每个线程对象独立运行,资源共享需额外处理
- 受单继承限制,扩展性较弱
1.2 实现Runnable接口方式
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Runnable running: " + Thread.currentThread().getName());}
}public class RunnableDemo {public static void main(String[] args) {MyRunnable task = new MyRunnable();new Thread(task, "Thread-1").start();new Thread(task, "Thread-2").start();}
}
优势:
- 避免单继承限制,保持类设计灵活性
- 同一任务对象可被多个线程共享
- 更符合"组合优于继承"的设计原则
1.3 关键对比
特性 | Thread类 | Runnable接口 |
---|---|---|
实现方式 | 继承类 | 实现接口 |
资源共享 | 需独立实例 | 可共享实例 |
锁机制 | 对象锁 | 需显式同步 |
线程池适配 | 需转换为Runnable | 直接适配 |
二、sleep()与wait()深度解析
2.1 核心区别对比
特性 | sleep() | wait() |
---|---|---|
方法归属 | Thread类(静态方法) | Object类(实例方法) |
锁行为 | 不释放锁 | 释放对象锁 |
唤醒机制 | 时间到期自动唤醒 | 需notify()/notifyAll() |
使用场景 | 定时任务/延迟操作 | 线程协作/条件等待 |
异常处理 | 抛出InterruptedException | 同上 |
2.2 典型使用场景
sleep()示例(定时任务):
// 每秒执行一次任务
while (true) {processTask();try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}
}
wait()示例(生产者-消费者):
class SharedQueue {private final List<Object> queue = new ArrayList<>();public synchronized void put(Object obj) {while (queue.size() == MAX_SIZE) {try {wait(); // 队列满时等待} catch (InterruptedException e) {}}queue.add(obj);notifyAll();}public synchronized Object take() {while (queue.isEmpty()) {try {wait(); // 队列空时等待} catch (InterruptedException e) {}}Object obj = queue.remove(0);notifyAll();return obj;}
}
2.3 注意事项
- wait()必须在同步代码块内调用
- 优先使用notifyAll()而非notify()避免死锁
- sleep()时间精度受系统调度影响
- 避免在循环外使用wait()
三、线程安全经典案例分析
3.1 非原子操作引发的问题
class Counter {private int count = 0;public void increment() {count++; // 非原子操作(load→add→store)}public int getCount() {return count;}
}public class UnsafeDemo {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + counter.getCount()); // 可能小于20000}
}
3.2 解决方案对比
同步方法:
public synchronized void increment() {count++;
}
同步代码块:
public void increment() {synchronized (this) {count++;}
}
原子类:
import java.util.concurrent.atomic.AtomicInteger;class SafeCounter {private AtomicInteger count = new AtomicInteger(0);public void increment() {count.incrementAndGet();}public int getCount() {return count.get();}
}
3.3 性能优化策略
- 缩小同步范围:
// 不推荐
public synchronized void process() {// 非共享操作...count++;
}// 推荐
public void process() {// 非共享操作...synchronized (this) {count++;}
}
- 锁分离技术:
class AdvancedCounter {private final Object readLock = new Object();private final Object writeLock = new Object();private int count = 0;public void increment() {synchronized (writeLock) {count++;}}public int getCount() {synchronized (readLock) {return count;}}
}
- 使用并发容器:
// 替代synchronizedList
List<String> list = new CopyOnWriteArrayList<>();// 替代synchronizedMap
Map<String, Integer> map = new ConcurrentHashMap<>();
四、多线程最佳实践
- 优先使用Runnable接口实现多线程
- 尽可能缩小同步代码块范围
- 优先使用并发工具类替代synchronized
- 避免在循环外使用wait()/notify()
- 合理使用线程池管理线程生命周期
- 对竞争资源使用原子类操作
- 始终考虑线程安全问题,即使看似安全的只读操作
通过本文的系统讲解,相信读者已经掌握了Java多线程编程的核心概念和实战技巧。在实际开发中,建议结合JUC包中的高级并发工具(如Lock、Semaphore、CountDownLatch等)来构建更高效稳定的多线程应用。