JUC(集合框架)
Java支持同步和并发多种集合框架,并发框架往往能带来更高的性能
同步类容器是 线程安全 的,如 Vector、HashTable 等容器的同步功能都是由 Collections.synchronizedMap
等工厂方法去创建实现的,底层使用 synchronized 关键字,每次只有一个线程访问容器
并发框架通常采用更好的设计来减小锁的粒度,提升吞吐;比如ConcurrentHashMap
将数据分段加锁,利用读写加锁的特性代替Vector
框架对比
ConcurrentHashMap | 替代 HashTable |
---|---|
ConcurrentSkipListMap | 排序 |
CopyOnWriteArrayList | 替代 Vector |
ConcurrentLinkedQueue | 高性能队列,无阻塞 |
LinkedBlockingQueue | 阻塞形式队列,阻塞 |
ConcurrentMap 容器#
ConcurrentHashMap JDK7中 容器内部使用(Segment)来表示不同的部分,每个段其实就是一个小的 HashTable ,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。把一个整体分成了16个段(Segment)。也就是最高支持16个线程的并发修改操作。这也是在多线程场景时 减小锁的粒度从而降低锁竞争 一种方案。并且代码中大多共享变量使用 volatile 关键字声明,目的是第一时间获取修改的内容,性能非常好
JDK8中采用CAS 和 synchronized 来实现,并且能通过多线程协助扩容
ConcurrentMap 接口下两个重要的实现:
- ConcurrentHashMap
- ConcurrentSkipListMap(排序)
Copy-On-Write 容器#
CopyOnWrite 容器既写时复制的容器, 用于读多写少的场景 。往一个容器添加元素时,不直接往当前容器添加,而是先将当前容器 Copy ,复制一个新的容器,然后往新的容器添加元素,添加完成之后,再将原容器的引用指向新的容器,这样做的好处是可以对 CopyOnWrite 容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以 CopyOnWrite 容器是一种读写分离的思想,读和写不同的容器。
// Copy-On-Write 容器是一种读写分离的思想
public class CopyOnWriteArrayList<E> {
//Copy-On-Write 容器写操作时加锁,写操作结束后解锁
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();//加锁
try {
//1. 获取原容器
Object[] elements = getArray();
int len = elements.length;
//2. 原容器 -> Copy -> 新容器
Object[] newElements = Arrays.copyOf(elements, len + 1);
//3. 往新容器写入内容
newElements[len] = e;
//4. 指向新容器
setArray(newElements);
return true;
} finally {
lock.unlock();//解锁
}
}
//读操作没有加锁,可以支持并发操作
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
public E get(int index) {
return get(getArray(), index);
}
}
Copy-On-Write 容器下两个重要的实现:
- CopyOnWriteArrayList
- CopyOnWriteArraySet
ConcurrentLinkedQueue 无阻塞队列#
适合高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常 ConcurrentLinkedQueue 性能好于 LinkedBlockingQueue。它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则,且不允许 null 元素。
内部也是使用CAS的原理来实现
ConcurrentLinkedQueue 重要方法:
- add()和offer():添加元素(ConcurrentLinkedQueue 下两个方法无区别)。
- poll()和peek():取头元素节点,区别在于前者删除元素,后者不会。注意: 没有元素时返回 null,不会阻塞队列。
BlockingQueue 阻塞队列#
与 ConcurrentLinkedQueue 相比,BlockingQueue 是阻塞的,即,put 方法在队列满的时候会阻塞直到有队列成员被消费,take 方法在队列空的时候也会阻塞,直到有队列成员被放进来。
BlockingQueue API: BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:
- 第一种是抛出一个异常
- 第二种是返回一个特殊值(null 或 false,具体取决于操作)
- 第三种是在操作可以成功前,无限期地阻塞当前线程
- 第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:
操作 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | 不可用 | 不可用 |
总结#
在并发场景中,合理的使用Java提供的并发框架能为系统性能带来质的提升。要熟练运用相关框架
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了