Java并发-理论基础
Java并发-理论基础
核心理论
1.1 并发与并行的区别
并发是指多个任务在同一时间段内交替执行,而并行则是指多个任务在同一时刻同时执行。在单核CPU中,只能实现并发;而在多核CPU中,可以实现并行。
1.2 线程与进程的关系
进程是操作系统进行资源分配的基本单位,线程是CPU调度的基本单位。一个进程可以包含多个线程,线程共享进程的内存空间和资源。
1.3 并发编程的挑战
- 线程安全问题:多个线程同时访问共享资源可能导致数据不一致
- 死锁:两个或多个线程互相等待对方释放资源
- 活锁:线程不断重复执行相同的操作,但无法继续前进
- 性能问题:线程创建、上下文切换等开销
代码实践
2.1 创建线程的三种方式
// 方式一:继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
// 方式二:实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}
// 方式三:使用Callable和Future
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
public class ThreadCreationExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 方式一
MyThread thread = new MyThread();
thread.start();
// 方式二
Thread runnableThread = new Thread(new MyRunnable());
runnableThread.start();
// 方式三
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get());
executor.shutdown();
}
}
2.2 线程安全的计数器实现
public class ThreadSafeCounter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
设计思想
3.1 并发编程模型
- 共享内存模型:线程通过共享内存进行通信,如Java
- 消息传递模型:线程通过发送消息进行通信,如Actor模型
3.2 不可变对象设计
不可变对象天生是线程安全的,因为它们的状态在创建后不会改变。在Java中,可以通过将类声明为final,所有字段声明为final来创建不可变对象。
3.3 线程池设计理念
线程池通过预先创建一定数量的线程,重用线程来减少线程创建和销毁的开销,提高系统性能。Java中的ThreadPoolExecutor是线程池的核心实现。
避坑指南
4.1 synchronized关键字的正确使用
- 避免在非静态方法上使用synchronized(this),可能导致死锁
- 不要同步String常量或基本类型的包装类对象
- 尽量减小同步代码块的范围
4.2 volatile关键字的局限性
volatile只能保证可见性和有序性,不能保证原子性。对于复合操作(如i++),仍需要使用synchronized或原子类。
4.3 ThreadLocal的内存泄漏风险
ThreadLocal如果使用不当,可能导致内存泄漏。需要注意在使用完ThreadLocal后调用remove()方法清除线程局部变量。
深度思考题
- 什么是Java内存模型(JMM)?它如何保证多线程的内存可见性、原子性和有序性?
- 乐观锁和悲观锁的区别是什么?在什么情况下应该使用乐观锁?
- 线程池的核心参数有哪些?如何合理配置线程池参数?
思考题回答:
Java内存模型(JMM)定义了线程和主内存之间的抽象关系,规定所有变量存储在主内存中,线程操作变量时需要将变量加载到工作内存中。JMM通过volatile、synchronized和final关键字以及Happens-Before规则来保证多线程的内存可见性、原子性和有序性。
乐观锁假设并发操作不会发生冲突,只在提交操作时检查是否有冲突;悲观锁则假设并发操作会发生冲突,在操作前先获取锁。乐观锁适用于读多写少的场景,如缓存更新;悲观锁适用于写多读少的场景,如数据库更新。
线程池的核心参数包括核心线程数、最大线程数、队列容量、拒绝策略等。配置线程池时需要考虑CPU核心数、任务类型(CPU密集型或IO密集型)、任务执行时间等因素。例如,CPU密集型任务的线程数可以设置为CPU核心数+1,IO密集型任务的线程数可以设置为CPU核心数*2。