高并发中的集合
1. 并发集合总览 🗺️
graph TD
A[并发集合] --> B[List相关]
A --> C[Map相关]
A --> D[Queue相关]
A --> E[Set相关]
B --> B1[CopyOnWriteArrayList]
B --> B2[Vector]
C --> C1[ConcurrentHashMap]
C --> C2[Hashtable]
D --> D1[ConcurrentLinkedQueue]
D --> D2[BlockingQueue]
E --> E1[CopyOnWriteArraySet]
E --> E2[ConcurrentSkipListSet]
2. 常用并发集合实现 📝
class ConcurrentCollectionExample {
// 1. ConcurrentHashMap
private val userCache = ConcurrentHashMap<String, User>()
// 2. CopyOnWriteArrayList
private val listeners = CopyOnWriteArrayList<EventListener>()
// 3. ArrayBlockingQueue
private val taskQueue = ArrayBlockingQueue<Runnable>(100)
// 4. ConcurrentLinkedQueue
private val messageQueue = ConcurrentLinkedQueue<Message>()
}
3. Android 中的应用场景 📱
// 1. 事件监听器管理
class EventManager {
// 使用CopyOnWriteArrayList避免并发修改异常
private val listeners = CopyOnWriteArrayList<EventListener>()
fun addEventListener(listener: EventListener) {
listeners.add(listener)
}
fun removeEventListener(listener: EventListener) {
listeners.remove(listener)
}
fun notifyListeners(event: Event) {
// 遍历时可以安全地添加/删除监听器
listeners.forEach { it.onEvent(event) }
}
}
// 2. 缓存实现
class MemoryCache {
// 使用ConcurrentHashMap实现线程安全的缓存
private val cache = ConcurrentHashMap<String, Bitmap>()
fun put(key: String, bitmap: Bitmap) {
cache.put(key, bitmap)
}
fun get(key: String): Bitmap? {
return cache.get(key)
}
fun remove(key: String) {
cache.remove(key)
}
}
// 3. 消息队列
class MessageQueue {
// 使用LinkedBlockingQueue实现生产者-消费者模式
private val queue = LinkedBlockingQueue<Message>(100)
fun sendMessage(message: Message) {
queue.offer(message)
}
fun processMessages() {
while (true) {
val message = queue.take() // 阻塞等待消息
processMessage(message)
}
}
}
4. 各集合类特点对比 📊
// 1. ConcurrentHashMap vs HashMap
class MapComparison {
// 线程不安全
private val hashMap = HashMap<String, String>()
// 线程安全,支持并发访问
private val concurrentMap = ConcurrentHashMap<String, String>()
fun compare() {
// ConcurrentHashMap的优势
// 1. 分段锁,提高并发性
// 2. 读操作无锁
// 3. 迭代器弱一致性
concurrentMap.forEach { (k, v) ->
// 遍历时可以修改
concurrentMap["newKey"] = "newValue"
}
}
}
// 2. CopyOnWriteArrayList vs ArrayList
class ListComparison {
// 线程不安全
private val arrayList = ArrayList<String>()
// 写时复制,适合读多写少场景
private val copyOnWriteList = CopyOnWriteArrayList<String>()
fun compare() {
// CopyOnWriteArrayList的特点
// 1. 写操作创建新数组
// 2. 读操作无锁
// 3. 适合监听器列表
}
}
5. 实际应用示例 🌟
// 1. 图片缓存
class ImageCache {
private val memoryCache = ConcurrentHashMap<String, WeakReference<Bitmap>>()
private val downloadQueue = LinkedBlockingQueue<String>()
fun loadImage(url: String, imageView: ImageView) {
// 1. 检查内存缓存
memoryCache[url]?.get()?.let {
imageView.setImageBitmap(it)
return
}
// 2. 异步下载
downloadQueue.offer(url)
}
private val downloadWorker = Thread {
while (true) {
val url = downloadQueue.take()
val bitmap = downloadImage(url)
memoryCache[url] = WeakReference(bitmap)
// 更新UI
}
}.apply { start() }
}
// 2. 事件总线
class EventBus {
private val subscribers =
ConcurrentHashMap<Class<*>, CopyOnWriteArraySet<(Any) -> Unit>>()
fun register(eventType: Class<*>, subscriber: (Any) -> Unit) {
subscribers.getOrPut(eventType) { CopyOnWriteArraySet() }
.add(subscriber)
}
fun post(event: Any) {
subscribers[event.javaClass]?.forEach { subscriber ->
subscriber(event)
}
}
}
// 3. 任务调度器
class TaskScheduler {
private val taskQueue = PriorityBlockingQueue<Task>()
private val workers = CopyOnWriteArrayList<Worker>()
fun scheduleTask(task: Task) {
taskQueue.offer(task)
}
inner class Worker : Thread() {
override fun run() {
while (!isInterrupted) {
val task = taskQueue.take()
task.execute()
}
}
}
}
6. 性能考虑 ⚡
graph LR
A[性能因素] --> B[并发度]
A --> C[内存开销]
A --> D[CPU开销]
B --> B1[锁粒度]
B --> B2[并发访问量]
C --> C1[空间复杂度]
C --> C2[GC压力]
D --> D1[同步开销]
D --> D2[复制开销]
7. 选择建议 💡
- 读多写少场景:
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- 高并发读写场景:
- ConcurrentHashMap
- ConcurrentLinkedQueue
- 需要阻塞特性:
- ArrayBlockingQueue
- LinkedBlockingQueue
- 需要排序:
- ConcurrentSkipListMap
- ConcurrentSkipListSet
8. Android 特定注意事项 ⚠️
- UI相关:
// 使用CopyOnWriteArrayList管理View监听器
class CustomView : View {
private val touchListeners = CopyOnWriteArrayList<OnTouchListener>()
fun addTouchListener(listener: OnTouchListener) {
touchListeners.add(listener)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
touchListeners.forEach { it.onTouch(this, event) }
return super.onTouchEvent(event)
}
}
- 内存管理:
// 使用WeakReference防止内存泄漏
class ImageLoader {
private val cache = ConcurrentHashMap<String, WeakReference<Bitmap>>()
fun clearCache() {
// 清理已被回收的引用
cache.entries.removeIf { it.value.get() == null }
}
}
- 性能优化:
// 根据场景选择合适的集合类型
class OptimizedCache {
// 小数据量,频繁修改
private val smallCache = HashMap<String, String>()
@Synchronized
fun accessSmallCache() {
// 使用同步块
}
// 大数据量,高并发
private val largeCache = ConcurrentHashMap<String, String>()
}
这些并发集合在 Android 开发中非常重要,能够:
- 保证线程安全
- 提高并发性能
- 简化并发编程
- 防止内存泄漏
底层原理
1. ConcurrentHashMap 的实现原理 🗺️
想象一个大型购物中心的储物柜系统:
/**
* ConcurrentHashMap 的核心实现
* - Node[] table 就像储物柜的格子
* - 分段锁就像不同区域的管理员
*/
class ConcurrentHashMap<K,V> {
// 1. 核心数据结构
private transient volatile Node<K,V>[] table;
// 2. Node节点(储物柜的一个格子)
static class Node<K,V> {
final int hash;
final K key;
volatile V value;
volatile Node<K,V> next;
}
// 3. put操作实现
//onlyIfAbsent 参数的意思是"只在键不存在时才放入值"
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// 计算hash值(找到储物柜区域)
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// CAS操作尝试放入值
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;
// 处理冲突
}
}
}
工作原理图示:
graph TD
A[ConcurrentHashMap] --> B[Segment 0]
A --> C[Segment 1]
A --> D[Segment N]
B --> B1[HashEntry]
B --> B2[HashEntry]
C --> C1[HashEntry]
C --> C2[HashEntry]
D --> D1[HashEntry]
D --> D2[HashEntry]
图的详细解释
这是 ConcurrentHashMap 在 JDK 1.7 版本的结构图,让我详细解释:
1. 分段锁设计
// JDK 1.7
public class ConcurrentHashMap<K,V> {
// 默认16个Segment
final Segment<K,V>[] segments;
static final class Segment<K,V> extends ReentrantLock {
// 每个Segment都是一个小的 HashMap
transient volatile HashEntry<K,V>[] table;
}
}
graph TD
A[ConcurrentHashMap] --> B[Segment 0]
A --> C[Segment 1]
A --> D[...Segment 15]
B --> E[独立的ReentrantLock锁]
C --> F[独立的ReentrantLock锁]
D --> G[独立的ReentrantLock锁]
2. 结构解析
2.1 第一层:ConcurrentHashMap
// 整个Map被分成多个Segment
Segment[] segments = new Segment[16]; // 默认16个
2.2 第二层:Segment(段)
class Segment extends ReentrantLock {
// 每个Segment都是一个小型HashMap
// 都有自己的锁
HashEntry[] table;
int count; // 元素计数
}
2.3 第三层:HashEntry(节点)
static final class HashEntry<K,V> {
final K key; // 键
volatile V value; // 值
final int hash; // 哈希值
final HashEntry next; // 链表后继节点
}
3. 工作原理
graph TD
A[put操作] --> B{计算Segment位置}
B --> C[获取Segment锁]
C --> D[操作HashEntry]
D --> E[释放锁]
F[get操作] --> G{计算Segment位置}
G --> H[直接读取]
4. 优势说明
// 1. 并发度
int concurrencyLevel = 16; // 默认16个Segment
// 最多同时支持16个线程并发写入
// 2. 锁粒度
// 每个Segment独立加锁
// 不同Segment之间可以并发操作
5. 形象比喻
想象一个商场:
- ConcurrentHashMap = 整个商场
- Segment = 不同的商铺区域
- HashEntry = 每个商铺
- 锁 = 每个区域的门锁
graph TD
A[商场] --> B[美食区]
A --> C[服装区]
A --> D[电器区]
B --> E[商铺1]
B --> F[商铺2]
C --> G[商铺3]
C --> H[商铺4]
优点:
- 不同区域可以同时营业
- 一个区域装修不影响其他区域
- 提高整体效率
这就是为什么叫"分段锁",它把整个 Map 分成多个段,每个段独立加锁,提高并发性能。
注意:这是 JDK 1.7 的设计,在 JDK 1.8 中改用了 CAS + synchronized 的方式,不再使用分段锁设计。
JDK 1.8版本的实现
1. 基本结构
public class ConcurrentHashMap<K,V> {
// Node数组(类似于一个大型停车场)
transient volatile Node<K,V>[] table;
// Node节点(每个停车位)
static class Node<K,V> {
final int hash;
final K key;
volatile V value;
volatile Node<K,V> next;
}
}
2. 数据结构图
graph TD
A[ConcurrentHashMap] --> B[数组]
B --> C[链表]
B --> D[红黑树]
subgraph "Node数组"
C --> C1[Node]
C --> C2[Node]
D --> D1[TreeNode]
D --> D2[TreeNode]
end
3. 并发控制
3.1 CAS操作(乐观锁)
// 就像停车场的电子显示屏
if (casTabAt(tab, i, null, node)) {
// CAS成功,相当于抢到了车位
} else {
// CAS失败,说明车位被占了
}
3.2 synchronized(悲观锁)
synchronized (f) { // f 是链表或树的首节点
// 对单个链表或红黑树进行操作
// 就像对单个车位区域进行管理
}
4. 形象比喻:智能停车场
graph LR
A[停车场入口] --> B{查看电子显示屏}
B -->|空位| C[直接停车]
B -->|占用| D[等待或找其他位置]
-
停车场 = ConcurrentHashMap
- 整个数组就像一个大停车场
- 每个数组位置就像一个停车区域
-
停车位 = Node节点
- 单个车位 = 单个Node
- 多层车位 = 链表
- 立体车库 = 红黑树
-
并发控制
- CAS = 电子显示屏(实时显示车位状态)
- synchronized = 区域管理(单个区域只允许一个人操作)
5. 具体操作示例
1. put 操作详解
public V put(K key, V value) {
// 1. 计算位置
int hash = hash(key);
int index = (table.length - 1) & hash; // 计算数组索引位置
// 2. 创建新节点
Node<K,V> newNode = new Node<>(hash, key, value, null);
// 3. 尝试插入
if (casTabAt(table, index, null, newNode)) {
// 如果这个位置是空的,CAS操作成功
// 就像看到空车位,直接停车
return null; // 表示新值插入成功
}
// 4. 处理哈希冲突
Node<K,V> first = tabAt(table, index); // 获取当前位置的节点
synchronized (first) { // 对这个位置加锁
// 4.1 再次检查该位置(因为可能已经被修改)
if (tabAt(table, index) == first) {
// 4.2 遍历链表或红黑树,更新或插入节点
// 就像找到对应的车位区域,管理员帮忙处理
}
}
}
2. 形象比喻:停车场场景
graph TD
A[来了一辆车] --> B{车位是空的吗?}
B -->|是| C[直接停车]
B -->|否| D[找管理员协调]
D --> E[管理员处理]
3. get 操作详解
public V get(Object key) {
// 1. 计算位置
int hash = hash(key);
int index = (table.length - 1) & hash;
// 2. 获取节点
Node<K,V> first = tabAt(table, index);
if (first == null) {
return null; // 位置空,没找到
}
// 3. 查找值
if (first.hash == hash &&
(first.key == key || key.equals(first.key))) {
return first.value; // 找到了,直接返回
}
// 4. 在链表或红黑树中继续查找
Node<K,V> e = first.next;
while (e != null) {
if (e.hash == hash &&
(e.key == key || key.equals(e.key))) {
return e.value;
}
e = e.next;
}
return null; // 没找到
}
4. 为什么 get 不需要加锁?
class Node<K,V> {
final K key; // key是不可变的
volatile V value; // value是volatile的
volatile Node<K,V> next; // next指针也是volatile的
}
原因:
- key 是 final 的,不会变
- value 是 volatile 的,保证可见性
- next 是 volatile 的,保证结构变化的可见性
就像:
- 车位号不会变(final key)
- 车辆状态实时可见(volatile value)
- 车位变化实时可见(volatile next)
5. 完整流程示例
ConcurrentHashMap<String, Car> parkingLot = new ConcurrentHashMap<>();
// 1. 停车
parkingLot.put("A001", new Car("宝马"));
// 内部流程:
// a. 计算车位号
// b. 如果车位空着,直接停(CAS)
// c. 如果有车,找管理员协调(synchronized)
// 2. 取车
Car car = parkingLot.get("A001");
// 内部流程:
// a. 计算车位号
// b. 直接查看车位,不需要管理员
// c. 因为所有信息都是实时可见的(volatile)
这样理解:
-
put 操作像停车:
- 先看车位空不空
- 空的就直接停
- 不空找管理员协调
-
get 操作像找车:
- 直接看车位号
- 因为所有信息都是实时更新的
- 所以不需要找管理员帮忙
6. 优化说明
与JDK 1.7相比:
// 1.7 版本:分段停车场
Segment[] segments; // 多个小停车场,各自管理
// 1.8 版本:统一大停车场
Node[] table; // 一个大停车场,统一管理
优势:
- 结构更简单(一层管理)
- 并发性能更好(粒度更细)
- 空间效率更高(不需要维护Segment)
7. 实际应用
ConcurrentHashMap<String, User> userCache = new ConcurrentHashMap<>();
// 多线程并发操作
void processUser(String userId, User user) {
// 1. 存储用户(停车)
userCache.put(userId, user);
// 2. 获取用户(取车)
User cachedUser = userCache.get(userId);
// 3. 原子操作(智能调度)
userCache.computeIfAbsent(userId, k -> new User());
}
就像一个现代化的智能停车场:
- 电子显示实时车位状态(CAS)
- 区域管理员协调复杂情况(synchronized)
- 高效有序地管理所有车位(Node数组 + 链表/红黑树)
这样的设计既保证了并发安全,又提供了优秀的性能!
2. CopyOnWriteArrayList 的实现原理 📝
想象一个共享的文档系统:
/**
* CopyOnWriteArrayList 的实现
* - 写时复制,就像修改文档时创建新副本
* - 读操作不加锁,就像查看文档不需要权限
*/
class CopyOnWriteArrayList<E> {
// 1. 底层数组
private transient volatile Object[] array;
// 2. 添加元素
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); // 获取写锁
try {
// 复制原数组
Object[] elements = getArray();
int len = elements.length;
// 创建新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 添加新元素
newElements[len] = e;
// 替换原数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
// 3. 读取元素(无锁)
public E get(int index) {
return get(getArray(), index);
}
}
写操作流程图:
sequenceDiagram
participant Client
participant Lock
participant OldArray
participant NewArray
Client->>Lock: 获取写锁
Client->>OldArray: 复制数组
Client->>NewArray: 修改新数组
Client->>OldArray: 替换旧数组
Client->>Lock: 释放写锁
3. BlockingQueue 的实现原理 🚦
想象一个餐厅的点餐系统:
/**
* ArrayBlockingQueue 实现
* - 队列就像餐厅的订单队列
* - 生产者就像收银员
* - 消费者就像厨师
*/
class ArrayBlockingQueue<E> {
// 1. 核心数据结构
final Object[] items;
int takeIndex; // 出队索引
int putIndex; // 入队索引
int count; // 元素数量
// 2. 入队操作
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await(); // 队列满时等待
enqueue(e); // 入队
} finally {
lock.unlock();
}
}
// 3. 出队操作
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await(); // 队列空时等待
return dequeue(); // 出队
} finally {
lock.unlock();
}
}
}
生产者-消费者模式图:
graph LR
A[生产者] -->|put| B[BlockingQueue]
B -->|take| C[消费者]
B -->|满| D[等待]
B -->|空| E[等待]
详细操作讲解
让我详细解释 ArrayBlockingQueue 的入队和出队操作:
1. 基本结构
public class ArrayBlockingQueue<E> {
// 底层数组
final Object[] items;
// 出队索引(takeIndex)
int takeIndex;
// 入队索引(putIndex)
int putIndex;
// 元素个数
int count;
}
2. 入队出队示意图
graph LR
A[putIndex<br>入队位置] --> B[数组]
B --> C[takeIndex<br>出队位置]
subgraph "循环数组"
D[0] --> E[1] --> F[2] --> G[3] --> H[4] --> D
end
3. 具体操作
// 1. 入队操作 (enqueue)
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x; // 放入putIndex位置
if (++putIndex == items.length) // putIndex到达末尾
putIndex = 0; // 回到数组开头
count++;
notEmpty.signal(); // 通知可以取数据了
}
// 2. 出队操作 (dequeue)
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; // 从takeIndex位置取出
items[takeIndex] = null;
if (++takeIndex == items.length) // takeIndex到达末尾
takeIndex = 0; // 回到数组开头
count--;
notFull.signal(); // 通知可以放数据了
return x;
}
4. 图解说明
graph TD
A[初始状态] --> B[数组: _ _ _ _]
C[入队1] --> D[数组: 1 _ _ _]
D --> E[putIndex移动]
F[入队2] --> G[数组: 1 2 _ _]
G --> H[putIndex移动]
I[出队] --> J[数组: _ 2 _ _]
J --> K[takeIndex移动]
5. 关键点
- 循环数组:
// 当索引到达数组末尾时,回到开头
if (++putIndex == items.length)
putIndex = 0;
if (++takeIndex == items.length)
takeIndex = 0;
- FIFO顺序:
// 先进先出
put: 1 2 3 -> [1 2 3]
take: [1 2 3] -> 1 2 3
6. 形象比喻
想象一个环形自助传送带:
- putIndex 是放餐点
- takeIndex 是取餐点
- 数组是传送带
- 当到达末尾时自动回到开头
graph TD
A[放餐点putIndex] --> B((传送带))
B --> C[取餐点takeIndex]
C --> B
7. 总结
-
不是头尾操作,而是:
- 入队:在 putIndex 位置放入元素
- 出队:从 takeIndex 位置取出元素
-
循环使用数组:
- 两个索引都会循环回到数组开头
- 形成一个逻辑上的环形队列
-
维护FIFO顺序:
- 先放入的元素一定先被取出
- 通过 takeIndex 和 putIndex 的移动保证顺序
就像一个环形自助餐传送带:
- 在固定位置放食物(putIndex)
- 在固定位置取食物(takeIndex)
- 传送带循环运转(循环数组)
4. ConcurrentSkipListMap 的实现原理 🎯
想象一个多层电梯系统:
/**
* ConcurrentSkipListMap 实现
* - 跳表结构就像不同层的快速通道
* - 查找过程就像乘坐快速电梯
*/
class ConcurrentSkipListMap<K,V> {
// 1. 节点结构
static final class Node<K,V> {
final K key;
volatile V value;
volatile Node<K,V> next;
}
// 2. 索引节点
static class Index<K,V> {
final Node<K,V> node;
final Index<K,V> down;
volatile Index<K,V> right;
}
// 3. 查找实现
private Node<K,V> findNode(Object key) {
outer: for (;;) {
for (Index<K,V> q = head, r = q.right, d;;) {
if (r != null) {
Node<K,V> n = r.node;
K k = n.key;
if (n.value == null) {
// 处理删除的情况
unlinkIndex(q, r);
break;
}
if (cpr(cmp, k, key) > 0)
q = r;
else
break;
}
if ((d = q.down) == null)
break outer;
q = d;
}
}
return null;
}
}
跳表结构图:
graph TD
L3[Level 3] --> 3A[1] --> 3B[4]
L2[Level 2] --> 2A[1] --> 2B[3] --> 2C[4]
L1[Level 1] --> 1A[1] --> 1B[2] --> 1C[3] --> 1D[4]
详细讲解
1. 基本概念
想象一个多层电梯系统:
class ConcurrentSkipListMap<K,V> {
// 最底层:包含所有节点
Node level0; // [1] -> [2] -> [3] -> [4] -> [5] -> [6]
// 上面的层:快速通道
Node level1; // [1] --------> [3] --------> [5]
Node level2; // [1] -----------------------> [5]
}
graph LR
A1[1] --> A3[3] --> A5[5]
subgraph "快速层"
end
B1[1] --> B2[2] --> B3[3] --> B4[4] --> B5[5] --> B6[6]
subgraph "基础层"
end
2. 形象比喻
就像一个大厦的电梯系统:
- 一层:停靠所有房间
- 快速层:只停靠部分房间
- 特快层:只停靠关键房间
3. 查找过程
每一层都是从小到大排序
// 查找值为4的节点
find(4) {
// 1. 从最高层开始
current = top; // 特快层
// 2. 逐层向下寻找
while (current.value < 4) {
if (current.next.value > 4) {
current = current.down; // 乘电梯下楼
} else {
current = current.next; // 在当前层前进
}
}
}
graph TD
A[开始] --> B[顶层]
B --> C{比4小?}
C -->|是| D[前进]
C -->|否| E[下楼]
D --> C
E --> C
4. 实际应用示例
ConcurrentSkipListMap<Integer, String> building =
new ConcurrentSkipListMap<>();
// 1. 添加房间
building.put(1, "101室");
building.put(2, "102室");
building.put(3, "103室");
// 2. 查找房间
String room = building.get(2); // 快速找到102室
// 3. 范围查询
// 获取1-3层的所有房间
Map<Integer, String> floors =
building.subMap(1, true, 3, true);
5. 优势特点
// 1. 并发安全
多个线程可以同时操作:
- 添加房间
- 删除房间
- 查找房间
// 2. 有序性
自动保持键的顺序:
- 1楼在最下面
- 2楼在中间
- 3楼在上面
// 3. 快速查找
通过多层结构加速查找:
- O(log n) 的平均查找时间
6. 使用场景
// 1. 排行榜系统
ConcurrentSkipListMap<Score, Player> rankings =
new ConcurrentSkipListMap<>();
// 2. 价格监控
ConcurrentSkipListMap<Price, Product> priceIndex =
new ConcurrentSkipListMap<>();
// 3. 定时任务调度
ConcurrentSkipListMap<Time, Task> scheduler =
new ConcurrentSkipListMap<>();
7. 生动比喻
想象一个智能电梯系统:
-
基础层(底层链表)
- 像普通电梯,停每一层
- 访问所有节点
-
快速层(跳跃层)
- 像快速电梯,跳过部分楼层
- 加速查找过程
-
并发访问
- 多部电梯同时运行
- 互不干扰
8. 总结
ConcurrentSkipListMap 就像:
- 一个多层电梯系统
- 底层可以到达所有房间
- 上层提供快速通道
- 多个人可以同时使用
- 自动保持顺序
适用于:
- 需要排序的场景
- 需要并发访问的场景
- 需要范围查询的场景
就像一个高效的智能大厦系统!
5. 在Android中的实际应用 📱
class ImageLoaderExample {
// 1. 图片缓存
private val memoryCache = ConcurrentHashMap<String, WeakReference<Bitmap>>()
// 2. 下载队列
private val downloadQueue = LinkedBlockingQueue<ImageRequest>()
// 3. 观察者列表
private val listeners = CopyOnWriteArrayList<ImageLoadListener>()
// 4. 优先级任务队列
private val priorityTasks = PriorityBlockingQueue<ImageTask>()
init {
// 启动工作线程
repeat(3) { startWorker() }
}
private fun startWorker() {
Thread {
while (!Thread.interrupted()) {
val request = downloadQueue.take()
processRequest(request)
}
}.start()
}
private fun processRequest(request: ImageRequest) {
// 1. 检查缓存
memoryCache[request.url]?.get()?.let { bitmap ->
notifyListeners(ImageLoadEvent(request.url, bitmap))
return
}
// 2. 下载图片
val bitmap = downloadImage(request.url)
// 3. 缓存图片
memoryCache[request.url] = WeakReference(bitmap)
// 4. 通知观察者
listeners.forEach { it.onImageLoaded(request.url, bitmap) }
}
}
6. 性能对比和选择指南 📊
graph TD
A[并发集合选择] --> B{读写比例?}
B -->|读多写少| C[CopyOnWriteArrayList]
B -->|读写均衡| D[ConcurrentHashMap]
B -->|写多读少| E[LinkedBlockingQueue]
C --> C1[优点: 读性能好]
C --> C2[缺点: 写开销大]
D --> D1[优点: 性能均衡]
D --> D2[缺点: 弱一致性]
E --> E1[优点: 写性能好]
E --> E2[缺点: 阻塞操作]
这些并发集合的选择需要考虑:
- 并发访问模式
- 数据一致性要求
- 性能要求
- 内存开销
在Android开发中,合理使用这些集合可以:
- 提高应用性能
- 保证数据安全
- 简化并发编程
- 优化内存使用
LinkedBlockingQueue和ArrayBlockingQueue的区别
graph TD
A[BlockingQueue] --> B[LinkedBlockingQueue]
A --> C[ArrayBlockingQueue]
B --> D[链表实现<br>默认容量Integer.MAX_VALUE]
C --> E[数组实现<br>固定容量]
D --> F[两把锁<br>takeLock/putLock]
E --> G[一把锁<br>lock]
1. 基本实现区别
// ArrayBlockingQueue: 基于数组,需要指定容量
val arrayQueue = ArrayBlockingQueue<String>(10)
// LinkedBlockingQueue: 基于链表,可以不指定容量
val linkedQueue = LinkedBlockingQueue<String>() // 默认Integer.MAX_VALUE
val linkedQueueWithCapacity = LinkedBlockingQueue<String>(10)
2. 锁的实现区别
class QueueExample {
// ArrayBlockingQueue: 一把锁
val arrayQueue = ArrayBlockingQueue<String>(10)
fun arrayQueueOperation() {
// put和take使用同一把锁
arrayQueue.put("数据") // 需要获取锁
arrayQueue.take() // 需要获取相同的锁
}
// LinkedBlockingQueue: 两把锁
val linkedQueue = LinkedBlockingQueue<String>(10)
fun linkedQueueOperation() {
// put和take使用不同的锁
linkedQueue.put("数据") // 只需要获取putLock
linkedQueue.take() // 只需要获取takeLock
}
}
3. 性能对比
class PerformanceTest {
fun testPerformance() {
// 高并发写入场景
val arrayQueue = ArrayBlockingQueue<Int>(1000)
val linkedQueue = LinkedBlockingQueue<Int>(1000)
// LinkedBlockingQueue性能更好
repeat(10) { threadId ->
thread {
repeat(1000) {
linkedQueue.put(it) // 不同线程的put/take可以并行
}
}
}
// ArrayBlockingQueue性能较差
repeat(10) { threadId ->
thread {
repeat(1000) {
arrayQueue.put(it) // 所有操作都要竞争同一把锁
}
}
}
}
}
4. 内存占用
class MemoryUsage {
fun memoryComparison() {
// ArrayBlockingQueue: 固定内存
val arrayQueue = ArrayBlockingQueue<String>(1000)
// 内存占用 = 数组大小 * 对象引用大小
// LinkedBlockingQueue: 动态内存
val linkedQueue = LinkedBlockingQueue<String>()
// 内存占用 = 节点数 * (对象引用大小 + 节点开销)
}
}
5. 使用场景
class UsageScenarios {
// 1. ArrayBlockingQueue适合
fun arrayQueueScenario() {
val queue = ArrayBlockingQueue<Task>(100)
// - 明确知道容量上限
// - 内存敏感
// - 需要公平性保证
}
// 2. LinkedBlockingQueue适合
fun linkedQueueScenario() {
val queue = LinkedBlockingQueue<Task>()
// - 不知道容量上限
// - 需要更好的并发性能
// - 生产消费速率差异大
}
}
主要区别总结:
- 实现结构:
/*
ArrayBlockingQueue:
- 基于数组
- 固定容量
- 循环数组实现
LinkedBlockingQueue:
- 基于链表
- 可变容量
- 节点动态创建
*/
- 锁机制:
/*
ArrayBlockingQueue:
- 单锁
- 同时只能有一个线程操作
- 适合简单场景
LinkedBlockingQueue:
- 双锁
- 读写可以并行
- 适合并发场景
*/
- 性能特点:
/*
ArrayBlockingQueue:
+ 内存占用固定
+ 适合小容量
- 并发性能较差
LinkedBlockingQueue:
+ 并发性能好
+ 容量灵活
- 内存占用较大
*/
选择建议:
- 明确容量用ArrayBlockingQueue
- 需要高并发用LinkedBlockingQueue
- 内存敏感用ArrayBlockingQueue
- 默认首选LinkedBlockingQueue
CAS乐观锁
1. CAS 基本概念
CAS (Compare And Swap) 是一种原子操作:
// 原理伪代码
boolean cas(期望值, 新值) {
if (当前值 == 期望值) {
当前值 = 新值;
return true; // 修改成功
} else {
return false; // 修改失败
}
}
2. casTabAt 方法解析
// ConcurrentHashMap 中的 casTabAt 方法
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// 参数说明:
tab // 数组
i // 索引位置
c // 期望值
v // 新值
3. 形象比喻:停车场场景
想象你是一个停车场管理员:
graph LR
A[查看车位] --> B{是空的吗?}
B -->|是| C[停车]
B -->|否| D[改找别处]
// 场景演示
停车场.casTabAt(车位数组, 第5号位, null, 新车)
// 就像:
if (第5号位确实是空的) {
停进新车; // CAS成功
return true;
} else {
另找车位; // CAS失败
return false;
}
4. 实际应用示例
// 1. 简单场景
public V put(K key, V value) {
// 尝试放入新节点
if (casTabAt(tab, i, null, node)) {
// 成功:车位是空的,直接停车
} else {
// 失败:车位被占了,需要其他处理
}
}
// 2. 完整示例
void putVal(K key, V value) {
Node<K,V>[] tab = table;
Node<K,V> node = new Node<>(key, value);
int i = hash(key) & (tab.length - 1); // 计算位置
// 首次尝试:CAS放入
if (casTabAt(tab, i, null, node)) {
// 成功
return;
}
// CAS失败后的处理
Node<K,V> f = tabAt(tab, i);
synchronized (f) {
// 同步处理冲突
}
}
5. 多线程场景演示
// 线程A和线程B同时尝试操作同一个位置
Thread A:
if (casTabAt(tab, 5, null, nodeA)) { // 成功
// 位置5原本是空的,A成功放入
}
Thread B:
if (casTabAt(tab, 5, null, nodeB)) { // 失败
// 位置5已经被A占了,B需要其他处理
}
6. 图解CAS操作
graph TD
A[开始CAS] --> B{检查当前值}
B -->|等于期望值| C[更新为新值]
B -->|不等于期望值| D[操作失败]
C --> E[返回true]
D --> F[返回false]
7. 优势说明
- 原子性
// CAS是原子操作,不会被中断
casTabAt(tab, i, null, node) // 要么成功,要么失败
- 无锁
// 不需要加锁,性能更好
// 比较 synchronized 更轻量级
- 乐观性
// 乐观地认为可能没有冲突
// 有冲突时才进行特殊处理
8. 实际应用建议
// 1. 首选CAS
if (casTabAt(tab, i, null, node)) {
// 快速路径
return;
}
// 2. CAS失败后再使用synchronized
synchronized (f) {
// 慢速路径
// 处理复杂情况
}
就像停车场管理:
- 先看车位是否空着(CAS检查)
- 是空的就直接停(CAS成功)
- 不是空的再找管理员协调(同步处理)
这样既保证了正确性,又兼顾了性能!
四个参数详解
1. 参数详解
static final <K,V> boolean casTabAt(
Node<K,V>[] tab, // 整个数组(相当于停车场)
int i, // 数组下标(相当于车位号)
Node<K,V> c, // 期望值(期望这个车位的状态)
Node<K,V> v // 新值(要放入的新车)
)
2. 形象比喻:停车场场景
graph TD
A[停车场tab] --> B[0号车位]
A --> C[1号车位]
A --> D[i号车位]
A --> E[n号车位]
D --> F[期望值c: 空]
D --> G[新值v: 新车]
3. 具体示例
// 场景一:往空车位停车
casTabAt(
parkingLot, // 停车场
5, // 5号车位
null, // 期望是空车位
new Car("宝马") // 要停的新车
)
// 场景二:更换车位上的车
casTabAt(
parkingLot, // 停车场
5, // 5号车位
oldCar, // 期望是旧车
newCar // 要换的新车
)
4. 实际代码示例
// 1. 插入新节点
Node<K,V>[] tab = table;
int i = hash(key) & (tab.length - 1); // 计算位置
// 尝试插入新节点
if (casTabAt(
tab, // 整个哈希表数组
i, // 计算出的位置
null, // 期望这个位置是空的
new Node<K,V>(key, value) // 要插入的新节点
)) {
// 插入成功
}
// 2. 更新已有节点
Node<K,V> oldNode = tabAt(tab, i);
if (casTabAt(
tab, // 整个哈希表数组
i, // 位置
oldNode, // 期望是旧节点
newNode // 要更新的新节点
)) {
// 更新成功
}
5. 参数作用图解
graph LR
A[casTabAt] --> B[tab数组]
A --> C[i索引]
A --> D[c期望值]
A --> E[v新值]
B --> F[存储空间]
C --> G[具体位置]
D --> H[当前状态]
E --> I[目标状态]
6. 类比现实场景
想象你是停车场管理员:
boolean parkCar(
停车场, // tab
5号车位, // i
空位, // c: 期望是空位
新车 // v: 要停的车
) {
// 原子性检查和更新
if (5号车位确实是空的) {
停入新车;
return true;
} else {
return false;
}
}
7. 使用场景
// 1. 初始插入
if (casTabAt(tab, i, null, newNode)) {
// 成功插入空位置
}
// 2. 更新现有值
if (casTabAt(tab, i, oldNode, newNode)) {
// 成功更新已有节点
}
// 3. 删除节点
if (casTabAt(tab, i, node, null)) {
// 成功删除节点
}
8. 总结
tab:就像整个停车场i:就像具体的车位号c:就像期望的车位状态(空/有车)v:就像要操作的目标(新车)
这四个参数配合完成:
- 在哪里(tab[i])
- 期望是什么(c)
- 要改成什么(v)
的原子性操作!
ReentrantLock可重入锁(悲观锁)
1. 基本概念
ReentrantLock 是可重入锁,意味着:
class SafeBox {
private ReentrantLock lock = new ReentrantLock();
public void openBox() {
lock.lock(); // 第一次获取锁
try {
doSomething(); // 可以再次获取同一把锁
} finally {
lock.unlock(); // 释放锁
}
}
private void doSomething() {
lock.lock(); // 第二次获取锁(重入)
try {
// 做一些事情
} finally {
lock.unlock(); // 释放锁
}
}
}
2. 形象比喻:带钥匙的房间
graph TD
A[进入房间] --> B[获取钥匙]
B --> C[开门进入]
C --> D[可以进入内室]
D --> E[离开时还钥匙]
就像:
- 拿到钥匙的人
- 可以用同一把钥匙
- 开启房间内的其他门
- 最后归还钥匙
3. 主要特性
3.1 可重入性
class BankVault {
private ReentrantLock lock = new ReentrantLock();
public void accessVault() {
lock.lock(); // 进入主库
try {
accessInnerVault(); // 可以继续进入内库
} finally {
lock.unlock();
}
}
private void accessInnerVault() {
lock.lock(); // 同一个线程可以再次获取锁
try {
// 访问内库
} finally {
lock.unlock();
}
}
}
3.2 公平性选择
// 公平锁:先来先服务
ReentrantLock fairLock = new ReentrantLock(true);
// 非公平锁:允许插队(默认)
ReentrantLock unfairLock = new ReentrantLock(false);
graph LR
A[公平锁] --> B[排队等待]
C[非公平锁] --> D[可能插队]
3.3 可中断性
class TimeoutRoom {
private ReentrantLock lock = new ReentrantLock();
public void enterRoom() throws InterruptedException {
// 等待有限时间
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 进入房间
} finally {
lock.unlock();
}
} else {
// 等待超时,执行其他操作
}
}
}
4. 实际应用示例
4.1 银行转账
class BankAccount {
private ReentrantLock lock = new ReentrantLock();
private double balance;
public void transfer(BankAccount target, double amount) {
// 获取两个锁
lock.lock();
try {
target.lock.lock();
try {
// 转账操作
this.balance -= amount;
target.balance += amount;
} finally {
target.lock.unlock();
}
} finally {
lock.unlock();
}
}
}
4.2 限时等待
class Restaurant {
private ReentrantLock tableLock = new ReentrantLock();
public void bookTable() {
try {
// 最多等待10分钟
if (tableLock.tryLock(10, TimeUnit.MINUTES)) {
try {
// 预订成功
} finally {
tableLock.unlock();
}
} else {
// 等待超时,订位失败
}
} catch (InterruptedException e) {
// 等待被中断
}
}
}
5. 与 synchronized 对比
// 1. synchronized(内置锁)
synchronized void method() {
// 简单,但功能有限
}
// 2. ReentrantLock(显式锁)
void method() {
lock.lock();
try {
// 更灵活,功能更多
} finally {
lock.unlock();
}
}
6. 高级特性
6.1 条件变量
class Buffer {
private ReentrantLock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (isFull()) {
notFull.await(); // 等待不满
}
// 放入数据
notEmpty.signal(); // 通知不空
} finally {
lock.unlock();
}
}
}
7. 使用建议
// 1. 始终使用 try-finally
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 业务代码
} finally {
lock.unlock(); // 确保释放锁
}
// 2. 考虑使用tryLock避免死锁
if (lock.tryLock(timeout, unit)) {
try {
// 获取锁成功
} finally {
lock.unlock();
}
}
8. 总结
ReentrantLock 就像是一个高级的门禁系统:
- 可重入:同一个人可以多次进入
- 可选公平:可以选择排队或插队
- 可中断:可以限时等待
- 更灵活:提供更多控制选项
使用场景:
- 需要高级功能时
- 需要灵活控制时
- 需要公平性选择时
- 需要可中断特性时
就像一个智能门禁系统,比普通的锁(synchronized)功能更强大!
乐观锁和悲观锁
1. 基本概念对比
// 悲观锁:假设一定会发生冲突
synchronized void pessimisticMethod() {
// 先上锁,其他人不能进来
doSomething();
}
// 乐观锁:假设不会发生冲突
void optimisticMethod() {
while(true) {
if (cas(期望值, 新值)) { // 先尝试,失败就重试
break;
}
}
}
2. 生活场景类比
想象一个餐厅场景:
graph TD
A[悲观锁方式] --> B[进门先排队]
B --> C[等位子空出来]
C --> D[就餐]
E[乐观锁方式] --> F[直接进门看有没有空位]
F --> G{有空位吗?}
G -->|有| H[直接就餐]
G -->|没有| F
3. 代码示例
// 1. 悲观锁示例(像排队等位)
synchronized void transfer(Account from, Account to, int amount) {
if (from.balance >= amount) {
from.balance -= amount;
to.balance += amount;
}
}
// 2. 乐观锁示例(像直接找空位)
boolean transfer(Account from, Account to, int amount) {
int oldBalance = from.balance;
if (oldBalance >= amount) {
return from.compareAndSet(
oldBalance, // 期望值
oldBalance - amount // 新值
);
}
return false;
}
4. 形象比喻
4.1 悲观锁:图书馆预约系统
// 必须提前预约座位
synchronized void readBook() {
// 1. 锁定座位
// 2. 看书
// 3. 释放座位
}
4.2 乐观锁:咖啡厅找位子
boolean sitAndDrink() {
while(true) {
Seat seat = findEmptySeat();
if (cas(seat, EMPTY, OCCUPIED)) { // 看到空位就直接坐
return true;
}
// 有人抢先坐了,继续找其他位置
}
}
5. 实际应用
// 1. AtomicInteger就是乐观锁的典型应用
AtomicInteger count = new AtomicInteger(0);
// 乐观地增加计数
count.incrementAndGet(); // 内部用CAS实现
// 2. ConcurrentHashMap中的应用
if (casTabAt(tab, i, null, node)) {
// 乐观地尝试插入
} else {
// 失败后才使用同步
synchronized (f) {
// 处理冲突
}
}
6. 优缺点分析
graph TD
A[乐观锁] --> B[优点]
A --> C[缺点]
B --> D[并发性能好]
B --> E[不会死锁]
C --> F[失败需要重试]
C --> G[可能饥饿]
7. 使用场景
// 1. 适合乐观锁:冲突少
class ViewCounter {
private AtomicInteger views = new AtomicInteger();
public void increment() {
views.incrementAndGet(); // 观看次数+1,冲突概率小
}
}
// 2. 适合悲观锁:冲突多
class BankAccount {
private double balance;
synchronized void withdraw(double amount) {
// 取款操作,冲突概率大
}
}
8. 总结
乐观锁就像是:
- 不先排队,直接去看有没有座位
- 有空位就坐,没空位就再找
- 适合"碰运气"的场景
特点:
- 乐观:假设不会冲突
- 无锁:不阻塞其他线程
- 重试:失败就再试
就像是一个乐观的人:
- 不会一开始就担心问题
- 先尝试,有问题再解决
- 失败了就再试一次
这种方式在并发冲突较少的场景下,性能会更好!
Volatile的使用
1. 基本概念
public class Coffee {
// 咖啡是否准备好
private volatile boolean ready = false;
// 咖啡温度
private volatile int temperature;
}
2. 三大特性
graph TD
A[volatile特性] --> B[可见性]
A --> C[有序性]
A --> D[单次读写原子性]
B --> E[所有线程立即看到最新值]
C --> F[禁止指令重排]
D --> G[单个volatile变量的读写具有原子性]
3. 形象比喻
3.1 可见性:咖啡店的电子公告板
class CoffeeShop {
private volatile boolean isOpen = false;
// 老板:更改营业状态
void changeStatus() {
isOpen = true; // 所有顾客立即看到
}
// 顾客:查看状态
boolean isShopOpen() {
return isOpen; // 总能看到最新状态
}
}
3.2 有序性:火车调度系统
class TrainSystem {
private volatile boolean signalReady;
private volatile boolean trainReady;
void prepare() {
trainReady = true; // 1. 火车就位
signalReady = true; // 2. 信号准备
// volatile保证这个顺序不会被改变
}
}
3.3 原子性:红绿灯
class TrafficLight {
private volatile String color = "RED";
// 单个volatile变量的读写是原子的
void changeLight(String newColor) {
color = newColor; // 原子操作
}
}
4. 常见应用场景
4.1 状态标志
public class Task implements Runnable {
private volatile boolean stopped = false;
public void run() {
while (!stopped) {
// 执行任务
}
}
public void stop() {
stopped = true; // 其他线程立即可见
}
}
4.2 双重检查锁定
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
5. 生动示例:餐厅点餐系统
class Restaurant {
// 菜品准备状态
private volatile boolean dishReady = false;
// 厨师线程
class Chef implements Runnable {
public void run() {
cooking();
dishReady = true; // 所有服务员立即知道
}
}
// 服务员线程
class Waiter implements Runnable {
public void run() {
while (!dishReady) {
// 等待菜品
}
serve(); // 立即看到菜品准备好
}
}
}
6. 注意事项
// 1. volatile不保证组合操作的原子性
class Counter {
private volatile int count = 0;
// 这个操作不是原子的!
public void increment() {
count++; // 读取、增加、写入三个操作
}
}
// 2. 正确的做法
class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子操作
}
}
7. 实际应用建议
// 1. 适合用volatile的场景
class Configuration {
private volatile String config; // 配置信息
public void updateConfig(String newConfig) {
config = newConfig; // 所有线程立即看到更新
}
}
// 2. 不适合用volatile的场景
class BankAccount {
private volatile int balance; // 不适合!需要原子性
public void transfer(int amount) {
balance += amount; // 需要使用synchronized或AtomicInteger
}
}
8. 总结
volatile就像是:
-
电子公告板(可见性)
- 一改就都看见
- 永远看到最新的
-
火车调度(有序性)
- 严格按顺序执行
- 不能随意调整顺序
-
红绿灯(原子性)
- 单个操作是原子的
- 但不保证组合操作
使用场景:
- 状态标志
- 一次性安全发布
- 独立观察变量
- 双重检查锁定
就像是一个"及时通知系统",确保所有线程都能立即看到最新信息!
tryLock的使用
主要是设置锁的超时 防止长时间持有锁的情况下的锁死问题
class TryLockExample {
private val lock = ReentrantLock()
fun example() {
try {
// 尝试获取锁,等待2秒
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
// 获得锁后的操作
doSomething()
} finally {
// 一定要在 finally 中释放锁
lock.unlock()
}
} else {
// 获取锁失败的处理
handleLockFailure()
}
} catch (e: InterruptedException) {
// 处理中断异常
}
}
// 不带超时的 tryLock
fun quickExample() {
if (lock.tryLock()) { // 立即返回结果
try {
doSomething()
} finally {
lock.unlock()
}
} else {
// 无法获取锁时的替代方案
handleLockFailure()
}
}
}
关键点:
tryLock()立即返回,不会阻塞tryLock(time, unit)可设置超时时间- 获取锁成功返回 true,失败返回 false
- 必须在 finally 中释放锁
- 比 synchronized 更灵活
防锁死处理流程图:
flowchart TD
A[开始] --> B{是否需要多个锁?}
B -->|是| C[使用有序加锁]
B -->|否| D[使用单锁]
C --> E{是否可能长时间持有锁?}
E -->|是| F[设置锁超时]
E -->|否| G[使用 synchronized]
F --> H[使用 tryLock]
G --> I[结束]
H --> I
D --> I