好的,接下来我们将逐部分深入讲解 Java Util Concurrent (JUC) 的内容。我们将从 JUC 概述 开始,逐步深入到具体的类和用法。
1. JUC 概述
1.1 JUC 的背景与目的
Java Util Concurrent(JUC)是 Java 5 引入的一个并发编程框架。随着多核处理器的普及,单一线程的性能已无法满足现代应用程序的需求。JUC 旨在简化多线程编程,提高程序的并发性能,使开发人员能够更容易地实现高效的并发应用。
1.2 JUC 与传统同步机制的比较
在 JUC 出现之前,Java 的多线程主要依赖于 synchronized
关键字和 wait/notify
方法。虽然这些机制可以保证线程安全,但在高并发场景中,会造成以下问题:
- 性能瓶颈:锁竞争会导致线程阻塞,从而降低系统性能。
- 复杂性:手动管理线程的生命周期和同步状态容易导致错误。
JUC 提供了更高级的工具,如线程池、并发集合和同步工具类,帮助开发者高效地处理并发问题。
1.3 JUC 的设计理念
JUC 的设计理念包括:
- 减少锁的竞争:通过使用分段锁、非阻塞算法等方式,降低锁的竞争。
- 提高并发度:通过使用线程池、无锁数据结构等方式,提升并发能力。
- 简化多线程编程:提供高层次的 API,使得多线程编程更加简单和直观。
2. 线程池
2.1 Executor Framework
Executor Framework 提供了用于管理线程的工具,可以通过它更方便地创建和管理线程。
Executor 接口
Executor
接口定义了一个执行任务的单一方法:
void execute(Runnable command);
ExecutorService 接口
ExecutorService
是 Executor
的一个子接口,提供了更丰富的方法来管理和控制线程。
void shutdown();
List<Runnable> shutdownNow();
ScheduledExecutorService
ScheduledExecutorService
是 ExecutorService
的一个子接口,提供定时和周期性任务的执行功能。
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
2.2 线程池的创建与管理
可以通过 Executors
工具类创建不同类型的线程池。
示例:创建固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
示例:创建缓存线程池
ExecutorService executorService = Executors.newCachedThreadPool();
示例:创建定时任务线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
2.3 线程池的最佳实践
- 线程池的大小设置:一般来说,线程池的大小应设置为 CPU 核心数的 1.5 到 2 倍,具体情况可根据任务的性质进行调整。
- 任务提交与管理:使用
submit()
方法提交任务,可以返回Future
对象以获取任务执行的结果。
示例:使用线程池提交任务
ExecutorService executorService = Executors.newFixedThreadPool(5);
Future<Integer> future = executorService.submit(() -> {
// 执行一些计算任务
return 42;
});
try {
Integer result = future.get(); // 获取任务执行结果
System.out.println("Task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown(); // 关闭线程池
}
3. 并发集合
3.1 ConcurrentHashMap
ConcurrentHashMap
是线程安全的哈希表,它使用分段锁机制来提高并发性能。
特点
- 支持高并发读写操作。
- 不会在写入操作时锁定整个表,允许多个线程同时访问。
示例:使用 ConcurrentHashMap
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 1);
map.put("key2", 2);
// 使用 lambda 表达式遍历
map.forEach(1, (key, value) -> {
System.out.println(key + ": " + value);
});
3.2 CopyOnWriteArrayList
CopyOnWriteArrayList
是一个线程安全的 List,适合读多写少的场景。
特点
- 每次写操作都会复制一份底层数组,保证读操作的安全。
- 写操作成本较高,但读操作性能优越。
示例:使用 CopyOnWriteArrayList
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("item1");
list.add("item2");
// 迭代元素
for (String item : list) {
System.out.println(item);
}
3.3 BlockingQueue
BlockingQueue
是用于实现生产者-消费者模式的阻塞队列,可以控制线程之间的协作。
常见实现
ArrayBlockingQueue
:基于数组的有界阻塞队列。LinkedBlockingQueue
:基于链表的可选界限阻塞队列。
示例:使用 BlockingQueue
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
// 生产者
Runnable producer = () -> {
try {
for (int i = 0; i < 10; i++) {
queue.put(i); // 阻塞直到队列可用
System.out.println("Produced: " + i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
// 消费者
Runnable consumer = () -> {
try {
for (int i = 0; i < 10; i++) {
Integer value = queue.take(); // 阻塞直到队列中有元素
System.out.println("Consumed: " + value);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
// 启动线程
new Thread(producer).start();
new Thread(consumer).start();
3.4 ConcurrentSkipListMap 和 ConcurrentSkipListSet
ConcurrentSkipListMap
和ConcurrentSkipListSet
是线程安全的有序集合,支持高效的并发访问和排序操作。
4. 同步工具类
4.1 CountDownLatch
CountDownLatch
用于在一个或多个线程中等待其他线程的完成。
使用示例
CountDownLatch latch = new CountDownLatch(3);
// 创建线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
Thread.sleep(1000); // 模拟工作
System.out.println("Task completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 计数减一
}
}).start();
}
// 主线程等待
try {
latch.await(); // 等待计数为零
System.out.println("All tasks completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
4.2 CyclicBarrier
CyclicBarrier
使多个线程在某个公共点上同步,当所有线程都到达这个点时,继续执行。
使用示例
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads reached the barrier, proceeding...");
});
// 创建线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " arrived at the barrier");
barrier.await(); // 等待其他线程到达
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
}).start();
}
4.3 Semaphore
Semaphore
用于控制对某个资源的访问数量,可以限制同时访问某个资源的线程数量。
使用示例
Semaphore semaphore = new Semaphore(2); // 允许 2 个线程同时访问
Runnable task = () -> {
try {
semaphore.acquire(); // 获取许可
System.out.println(Thread.currentThread().getName() + " acquired a permit");
Thread.sleep(1000); // 模拟工作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放许可
System.out.println(Thread.currentThread().getName() + " released a permit");
}
};
// 启动线程
for (int i = 0; i < 5; i++) {
new Thread(task).start();
}
4.4 Exchanger
Exchanger
允许两个线程在一个点交换数据,常
用于实现双向通信。
使用示例
Exchanger<String> exchanger = new Exchanger<>();
Runnable thread1 = () -> {
try {
String result = exchanger.exchange("Thread 1 Data");
System.out.println("Thread 1 received: " + result);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
Runnable thread2 = () -> {
try {
String result = exchanger.exchange("Thread 2 Data");
System.out.println("Thread 2 received: " + result);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
// 启动线程
new Thread(thread1).start();
new Thread(thread2).start();
5. 原子变量
5.1 Atomic 包介绍
Atomic
包提供了一系列的原子类,用于在多线程环境中实现安全的变量更新。主要包括:
AtomicInteger
AtomicLong
AtomicReference
5.2 原子操作的使用
原子操作使用 CAS(Compare-And-Swap)机制,在执行更新时避免了传统锁的开销。
示例:使用 AtomicInteger
AtomicInteger atomicInt = new AtomicInteger(0);
// 多个线程并发更新
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
atomicInt.incrementAndGet(); // 原子增加
}
};
// 启动多个线程
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
System.out.println("Final value: " + atomicInt.get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
6. 读写锁
6.1 ReentrantReadWriteLock
ReentrantReadWriteLock
允许多个线程同时读,但写操作是互斥的,适合读多写少的场景。
使用示例
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReadWriteLock readWriteLock = rwLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
Runnable readTask = () -> {
readWriteLock.lock();
try {
// 读取操作
System.out.println(Thread.currentThread().getName() + " is reading");
} finally {
readWriteLock.unlock();
}
};
Runnable writeTask = () -> {
writeLock.lock();
try {
// 写入操作
System.out.println(Thread.currentThread().getName() + " is writing");
} finally {
writeLock.unlock();
}
};
// 启动线程
new Thread(readTask).start();
new Thread(writeTask).start();
7. 线程安全的设计模式
7.1 单例模式的实现
单例模式可以通过多种方式实现线程安全的实例创建,常见方法包括懒汉式和饿汉式。
懒汉式单例(双重检查锁定)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
7.2 生产者-消费者模式
使用 JUC 实现生产者-消费者模型,可以利用 BlockingQueue
进行任务的协调。
示例:生产者-消费者模式
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
Runnable producer = () -> {
try {
for (int i = 0; i < 10; i++) {
queue.put(i);
System.out.println("Produced: " + i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
Runnable consumer = () -> {
try {
for (int i = 0; i < 10; i++) {
Integer value = queue.take();
System.out.println("Consumed: " + value);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
// 启动线程
new Thread(producer).start();
new Thread(consumer).start();
7.3 观察者模式
可以使用 java.util.concurrent
提供的工具类实现线程安全的观察者模式。
8. 并发调试与性能优化
8.1 常见的并发问题
- 死锁:多个线程在等待彼此释放资源时,导致程序无法继续执行。
- 活锁:线程之间相互干扰,导致程序一直处于活动状态,但无法完成工作。
- 饥饿:某些线程因资源竞争而无法获得足够的资源。
8.2 JUC 性能优化
- 使用无锁数据结构,如
ConcurrentHashMap
和CopyOnWriteArrayList
。 - 减少锁的持有时间,尽量缩小同步代码块的范围。
8.3 使用工具进行调试
- Java VisualVM:用于监控 Java 应用程序的性能。
- JConsole:用于监控 Java 应用程序的内存和线程使用情况。
9. JUC 的最佳实践
9.1 代码规范
- 在多线程代码中,确保使用恰当的同步机制。
- 保持代码简洁,避免复杂的同步逻辑。
9.2 使用文档与示例
- 在使用 JUC 组件时,查看官方文档和示例,以了解最佳用法。
9.3 避免常见错误
- 避免在锁持有期间调用外部方法,以减少死锁风险。
10. 实践项目
10.1 实现一个简单的并发任务调度器
- 设计一个支持并发执行和调度的任务管理器。
10.2 使用 JUC 解决实际业务问题
- 利用 JUC 的特性解决实际业务场景中的并发问题。