数据结构 - NIO高并发集合及相关锁

77 阅读24分钟

高并发中的集合

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. 选择建议 💡

  1. 读多写少场景
  • CopyOnWriteArrayList
  • CopyOnWriteArraySet
  1. 高并发读写场景
  • ConcurrentHashMap
  • ConcurrentLinkedQueue
  1. 需要阻塞特性
  • ArrayBlockingQueue
  • LinkedBlockingQueue
  1. 需要排序
  • ConcurrentSkipListMap
  • ConcurrentSkipListSet

8. Android 特定注意事项 ⚠️

  1. 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)
    }
}
  1. 内存管理
// 使用WeakReference防止内存泄漏
class ImageLoader {
    private val cache = ConcurrentHashMap<String, WeakReference<Bitmap>>()
    
    fun clearCache() {
        // 清理已被回收的引用
        cache.entries.removeIf { it.value.get() == null }
    }
}
  1. 性能优化
// 根据场景选择合适的集合类型
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. 形象比喻

想象一个商场:

  1. ConcurrentHashMap = 整个商场
  2. Segment = 不同的商铺区域
  3. HashEntry = 每个商铺
  4. 锁 = 每个区域的门锁
graph TD
    A[商场] --> B[美食区]
    A --> C[服装区]
    A --> D[电器区]
    
    B --> E[商铺1]
    B --> F[商铺2]
    C --> G[商铺3]
    C --> H[商铺4]

优点:

  1. 不同区域可以同时营业
  2. 一个区域装修不影响其他区域
  3. 提高整体效率

这就是为什么叫"分段锁",它把整个 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[等待或找其他位置]
  1. 停车场 = ConcurrentHashMap

    • 整个数组就像一个大停车场
    • 每个数组位置就像一个停车区域
  2. 停车位 = Node节点

    • 单个车位 = 单个Node
    • 多层车位 = 链表
    • 立体车库 = 红黑树
  3. 并发控制

    • 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的
}

原因:

  1. key 是 final 的,不会变
  2. value 是 volatile 的,保证可见性
  3. 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)

这样理解:

  1. put 操作像停车:

    • 先看车位空不空
    • 空的就直接停
    • 不空找管理员协调
  2. get 操作像找车:

    • 直接看车位号
    • 因为所有信息都是实时更新的
    • 所以不需要找管理员帮忙
6. 优化说明

与JDK 1.7相比:

// 1.7 版本:分段停车场
Segment[] segments;  // 多个小停车场,各自管理

// 1.8 版本:统一大停车场
Node[] table;       // 一个大停车场,统一管理

优势:

  1. 结构更简单(一层管理)
  2. 并发性能更好(粒度更细)
  3. 空间效率更高(不需要维护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());
}

就像一个现代化的智能停车场:

  1. 电子显示实时车位状态(CAS)
  2. 区域管理员协调复杂情况(synchronized)
  3. 高效有序地管理所有车位(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. 关键点
  1. 循环数组
// 当索引到达数组末尾时,回到开头
if (++putIndex == items.length)
    putIndex = 0;

if (++takeIndex == items.length)
    takeIndex = 0;
  1. 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. 总结
  1. 不是头尾操作,而是:

    • 入队:在 putIndex 位置放入元素
    • 出队:从 takeIndex 位置取出元素
  2. 循环使用数组

    • 两个索引都会循环回到数组开头
    • 形成一个逻辑上的环形队列
  3. 维护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. 形象比喻

就像一个大厦的电梯系统:

  1. 一层:停靠所有房间
  2. 快速层:只停靠部分房间
  3. 特快层:只停靠关键房间
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. 生动比喻

想象一个智能电梯系统:

  1. 基础层(底层链表)

    • 像普通电梯,停每一层
    • 访问所有节点
  2. 快速层(跳跃层)

    • 像快速电梯,跳过部分楼层
    • 加速查找过程
  3. 并发访问

    • 多部电梯同时运行
    • 互不干扰
8. 总结

ConcurrentSkipListMap 就像:

  1. 一个多层电梯系统
  2. 底层可以到达所有房间
  3. 上层提供快速通道
  4. 多个人可以同时使用
  5. 自动保持顺序

适用于:

  • 需要排序的场景
  • 需要并发访问的场景
  • 需要范围查询的场景

就像一个高效的智能大厦系统!

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[缺点: 阻塞操作]

这些并发集合的选择需要考虑:

  1. 并发访问模式
  2. 数据一致性要求
  3. 性能要求
  4. 内存开销

在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>()
        // - 不知道容量上限
        // - 需要更好的并发性能
        // - 生产消费速率差异大
    }
}

主要区别总结:

  1. 实现结构:
/*
ArrayBlockingQueue:
- 基于数组
- 固定容量
- 循环数组实现

LinkedBlockingQueue:
- 基于链表
- 可变容量
- 节点动态创建
*/
  1. 锁机制:
/*
ArrayBlockingQueue:
- 单锁
- 同时只能有一个线程操作
- 适合简单场景

LinkedBlockingQueue:
- 双锁
- 读写可以并行
- 适合并发场景
*/
  1. 性能特点:
/*
ArrayBlockingQueue:
+ 内存占用固定
+ 适合小容量
- 并发性能较差

LinkedBlockingQueue:
+ 并发性能好
+ 容量灵活
- 内存占用较大
*/

选择建议:

  1. 明确容量用ArrayBlockingQueue
  2. 需要高并发用LinkedBlockingQueue
  3. 内存敏感用ArrayBlockingQueue
  4. 默认首选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. 优势说明

  1. 原子性
// CAS是原子操作,不会被中断
casTabAt(tab, i, null, node)  // 要么成功,要么失败
  1. 无锁
// 不需要加锁,性能更好
// 比较 synchronized 更轻量级
  1. 乐观性
// 乐观地认为可能没有冲突
// 有冲突时才进行特殊处理

8. 实际应用建议

// 1. 首选CAS
if (casTabAt(tab, i, null, node)) {
    // 快速路径
    return;
}

// 2. CAS失败后再使用synchronized
synchronized (f) {
    // 慢速路径
    // 处理复杂情况
}

就像停车场管理:

  1. 先看车位是否空着(CAS检查)
  2. 是空的就直接停(CAS成功)
  3. 不是空的再找管理员协调(同步处理)

这样既保证了正确性,又兼顾了性能!

四个参数详解

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:就像要操作的目标(新车)

这四个参数配合完成:

  1. 在哪里(tab[i])
  2. 期望是什么(c)
  3. 要改成什么(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[离开时还钥匙]

就像:

  1. 拿到钥匙的人
  2. 可以用同一把钥匙
  3. 开启房间内的其他门
  4. 最后归还钥匙

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 就像是一个高级的门禁系统:

  1. 可重入:同一个人可以多次进入
  2. 可选公平:可以选择排队或插队
  3. 可中断:可以限时等待
  4. 更灵活:提供更多控制选项

使用场景:

  • 需要高级功能时
  • 需要灵活控制时
  • 需要公平性选择时
  • 需要可中断特性时

就像一个智能门禁系统,比普通的锁(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. 总结

乐观锁就像是:

  1. 不先排队,直接去看有没有座位
  2. 有空位就坐,没空位就再找
  3. 适合"碰运气"的场景

特点:

  1. 乐观:假设不会冲突
  2. 无锁:不阻塞其他线程
  3. 重试:失败就再试

就像是一个乐观的人:

  • 不会一开始就担心问题
  • 先尝试,有问题再解决
  • 失败了就再试一次

这种方式在并发冲突较少的场景下,性能会更好!

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就像是:

  1. 电子公告板(可见性)

    • 一改就都看见
    • 永远看到最新的
  2. 火车调度(有序性)

    • 严格按顺序执行
    • 不能随意调整顺序
  3. 红绿灯(原子性)

    • 单个操作是原子的
    • 但不保证组合操作

使用场景:

  • 状态标志
  • 一次性安全发布
  • 独立观察变量
  • 双重检查锁定

就像是一个"及时通知系统",确保所有线程都能立即看到最新信息!

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()
        }
    }
}

关键点:

  1. tryLock() 立即返回,不会阻塞
  2. tryLock(time, unit) 可设置超时时间
  3. 获取锁成功返回 true,失败返回 false
  4. 必须在 finally 中释放锁
  5. 比 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