Java-多线程并发-6.线程与集合


📌 同步与异步,阻塞与非阻塞详解

这四个概念经常在计算机科学和软件开发中出现,特别是在并发编程和系统设计中。理解它们的差异对于编写高效、可扩展的代码至关重要。让我们详细探讨每个概念:


1. 📜 同步 (Synchronous)

  • 📍 定义 当一个操作开始后,必须等待该操作完成后,才能开始下一个操作。
  • 📍 特点
    • 调用者主动等待操作的完成。
    • 结果是立即可知的。
    • 代码逻辑通常更简单直接。
  • 📍 示例
    • 从数据库中读取数据。
    • HTTP请求。

2. 📜 异步 (Asynchronous)

  • 📍 定义 一个操作的开始和结束是分离的,不需要等待完成。
  • 📍 特点
    • 调用者不必等待,可以继续其他操作。
    • 结果可能立即返回,也可能稍后通过回调、事件或其他机制返回。
    • 可能需要复杂的控制逻辑或错误处理。
  • 📍 示例
    • JavaScript中的AJAX请求。
    • Java中的FutureCompletableFuture.

3. 📜 阻塞 (Blocking)

  • 📍 定义 当某个操作不能立即完成时,会使调用者等待,直到该操作完成。
  • 📍 特点
    • 调用者不能继续执行,直到阻塞操作完成。
    • 在多线程环境中,其他线程可能仍然继续执行。
  • 📍 示例
    • 读取磁盘文件,如果文件还没准备好,操作会阻塞。
    • 在没有数据可用的情况下,从网络套接字读取。

4. 📜 非阻塞 (Non-blocking)

  • 📍 定义 即使操作不能立即完成,也不会阻止调用者继续执行。
  • 📍 特点
    • 调用者可以继续执行其他操作,不必等待。
    • 如果操作不能立即完成,通常会返回一个状态,告知调用者稍后重试。
  • 📍 示例
    • 非阻塞I/O操作。
    • 数据库的轮询查询。

⚠️ 注意

  • 同步和异步通常描述的是操作的完成时间与其开始时间的关系。
  • 阻塞和非阻塞通常描述的是操作的开始是否会暂停调用者的执行。

在设计系统或写代码时,选择合适的同步/异步和阻塞/非阻塞策略是关键。选择的策略会影响性能、可扩展性以及代码的复杂性。


🌟 CAS (Compare-And-Swap) 详解


  • 📖 定义

    • CAS是一种无锁算法。它是一种原子操作,用于在多线程环境下管理变量的并发操作。
    • CAS操作包括三个参数:一个内存位置(V)、预期原值(A)和新值(B)。
    • 如果内存位置V的值与预期原值A相匹配,则将该位置的值更新为B,否则不做任何操作。
    • 典型的CAS操作返回一个布尔值或者之前的值,以指示操作是否成功。
  • 🖥️ 工作原理

      1. 📜 从内存位置V读取当前值。
      1. 🔍 比较当前值与预期值A。
      1. 💡 如果值相等,使用新值B更新内存位置V的值。
      1. 🔄 如果不相等,可能需要重新尝试,或者返回失败。
  • 💥 优势

    • 🚫 无锁 CAS不需要传统的锁机制来实现并发控制。
    • 🚀 性能 在某些场景下,尤其是低竞争的环境中,CAS比锁具有更好的性能。
    • 🔒 线程安全 CAS提供了一种方法,可以确保数据在多线程环境中的安全性。
  • 🚧 限制和问题

      1. 🔄 自旋 如果不成功,可能需要多次尝试。在高竞争的环境中,可能会导致大量的自旋。
      1. 📛 ABA问题 如果V的值在A和B之间变化,然后又回到了A,CAS会认为它从未改变过。这是一个潜在的问题,可能需要额外的机制来处理。
      1. 🔍 只能保证一个共享变量的原子操作 对于多个共享变量,CAS无法做到原子性。
  • 🔧 Java中的实现

    • 📚 Java的java.util.concurrent.atomic包为多种变量类型(如AtomicIntegerAtomicLong等)提供了CAS操作。
    • 💻 在Java中,CAS是通过原生方法(如Unsafe.compareAndSwapInt)实现的。

⚠️ 注意 尽管CAS提供了一个无锁的并发解决方案,但它并不总是最佳选择。在某些场景下,传统的锁或其他并发工具可能更为适合。


🌟 使用Concurrent集合

在Java中,java.util.concurrent包提供了一系列线程安全的集合类,这些集合类被设计用于支持高并发操作,而无需使用外部同步。以下是一些常用的Concurrent集合:


1. 📂 List

  • 📜 CopyOnWriteArrayList:
    • 常用方法
      • add(E e): 向列表的末尾添加一个元素。
      • addIfAbsent(E e): 如果列表中尚未存在该元素,则添加该元素。
      • remove(Object o): 从列表中移除第一个出现的指定元素。
      • get(int index): 返回列表中指定位置的元素。
      • set(int index, E element): 替换列表中指定位置的元素。
      • size(): 返回列表中的元素数量。
    • 特点
      • 读操作无锁,写操作会创建数组的新副本。
      • 适用于读多写少的场景。
      • 写操作的开销较大,因为每次写都需要复制整个数组。
    • 结构
      • 基于数组。
    • 原理
      • 采用“写时复制”策略,即当列表被修改时(如添加、删除元素),会创建该列表的一个新副本。
    • 使用场景及示例 读操作远多于写操作的高并发场景。实时读取配置信息。
      • 实时读取配置信息
        class ConfigManager {
            private final CopyOnWriteArrayList<String> configItems = new CopyOnWriteArrayList<>();
        
            public void addConfig(String config) {
                configItems.add(config);
            }
        
            public List<String> getConfigs() {
                return configItems;
            }
        }
        
        ConfigManager manager = new ConfigManager();
        manager.addConfig("DATABASE_URL=jdbc:mysql://localhost:3306/mydb");
        List<String> currentConfig = manager.getConfigs();
        

2. 📂 Set

  • 📜 CopyOnWriteArraySet:
    • 常用方法
      • add(E e): 向集合中添加一个元素,如果集合已经包含此元素,则不进行任何操作。
      • remove(Object o): 从集合中移除一个元素。
      • contains(Object o): 判断集合是否包含指定的元素。
      • size(): 返回集合中的元素数量。
    • 特点CopyOnWriteArrayList类似。
    • 结构 基于CopyOnWriteArrayList
    • 原理 使用“写时复制”策略。
    • 使用场景及示例CopyOnWriteArrayList类似,但不需要保持元素的顺序。
      • 注册监听器或订阅者
        class EventDispatcher {
            private final CopyOnWriteArraySet<EventListener> listeners = new CopyOnWriteArraySet<>();
        
            public void registerListener(EventListener listener) {
                listeners.add(listener);
            }
        
            public void dispatchEvent(Event event) {
                for (EventListener listener : listeners) {
                    listener.handleEvent(event);
                }
            }
        }
        

  • 📜 ConcurrentSkipListSet:
    • 常用方法
      • add(E e): 将指定元素添加到此集合。
      • remove(Object o): 从集合中移除指定元素的单个实例(如果存在)。
      • contains(Object o): 判断集合是否包含指定的元素。
      • first(): 返回此集合中的第一个(最低)元素。
      • last(): 返回此集合中的最后一个(最高)元素。
    • 特点
      • 线程安全。
      • 提供排序。
    • 结构 跳表。
    • 原理 使用跳表数据结构,每个节点都有多个指针指向不同的层。
    • 使用场景及示例 需要排序的线程安全的Set。
      • 排行榜应用
        class Scoreboard {
            private final ConcurrentSkipListSet<PlayerScore> scores = new ConcurrentSkipListSet<>();
        
            public void addScore(PlayerScore score) {
                scores.add(score);
            }
        
            public Set<PlayerScore> getTopScores(int n) {
                return scores.headSet(new PlayerScore("", n));
            }
        }
        

3. 📂 Queue

  • 📜 ConcurrentLinkedQueue:
    • 常用方法
      • offer(E e): 将指定的元素添加到此队列的尾部。
      • poll(): 检索并移除此队列的头部,如果此队列为空,则返回 null。
      • peek(): 检索但不移除此队列的头部,如果此队列为空,则返回 null。
    • 特点
      • 非阻塞。
      • 线程安全。
    • 结构 基于链接节点。
    • 原理 使用无锁算法实现。
    • 使用场景及示例 高并发场景中的非阻塞队列。
      • 无阻塞任务队列
        class TaskQueue {
            private final ConcurrentLinkedQueue<Task> tasks = new ConcurrentLinkedQueue<>();
        
            public void addTask(Task task) {
                tasks.offer(task);
            }
        
            public Task fetchTask() {
                return tasks.poll();
            }
        }
        

  • 📜 LinkedBlockingQueue:
    • 常用方法
      • offer(E e): 将指定的元素添加到此队列的尾部,如果可以立即执行而不需要等待。
      • take(): 检索并移除此队列的头部,如有必要,等待元素变得可用。
      • put(E e): 将指定的元素添加到此队列的尾部,如有必要,等待空间变得可用。
    • 特点
      • 阻塞。
      • 可选的容量。
    • 结构 基于链接节点。
    • 原理 使用内部锁和条件来实现阻塞操作。
    • 使用场景及示例 生产者-消费者模型中的任务队列。
      • 生产者-消费者模型中的任务队列
        class ProducerConsumerExample {
            private final LinkedBlockingQueue<Item> queue = new LinkedBlockingQueue<>(10);
        
            class Producer extends Thread {
                public void run() {
                    while (true) {
                        Item item = produceItem();
                        queue.put(item);
                    }
                }
            }
        
            class Consumer extends Thread {
                public void run() {
                    while (true) {
                        Item item = queue.take();
                        consumeItem(item);
                    }
                }
            }
        }
        

  • 📜 ArrayBlockingQueue:
    • 常用方法
      • offer(E e): 将指定的元素添加到此队列的尾部,如果可以立即执行而不需要等待。
      • take(): 检索并移除此队列的头部,如有必要,等待元素变得可用。
      • put(E e): 将指定的元素添加到此队列的尾部,如有必要,等待空间变得可用。
    • 特点
      • 有界。
      • 阻塞。
    • 结构 基于数组。
    • 原理 使用内部锁和条件来实现阻塞操作。
    • 使用场景 固定大小的任务队列。
    • 📍 使用场景及示例
      • 固定大小的任务队列
        class TaskManager {
            private final ArrayBlockingQueue<Task> taskQueue = new ArrayBlockingQueue<>(100);
        
            public void submitTask(Task task) throws InterruptedException {
                taskQueue.put(task); // 将任务放入队列,如果队列已满,这将阻塞
            }
        
            public Task fetchTask() throws InterruptedException {
                return taskQueue.take(); // 获取任务,如果队列为空,这将阻塞
            }
        }
        
        TaskManager manager = new TaskManager();
        manager.submitTask(new Task());
        Task task = manager.fetchTask();
        

⚠️ 注意 ArrayBlockingQueue 的容量是固定的,一旦创建,就不能更改。添加元素时,如果队列已满,操作将阻塞,直到队列中有可用空间。同样,如果尝试从空队列中取出元素,操作将阻塞,直到队列中有可用元素。


  • 📜 SynchronousQueue:
    • 常用方法
      • put(E e): 将指定的元素添加到此队列,等待另一个线程来接收它。
      • take(): 检索并移除此队列的头部,如有必要,等待元素变得可用。
    • 特点
      • 无存储空间。
      • 用于线程间的数据传输。
    • 结构 不存储元素。
    • 原理 一个线程的插入操作会等待另一个线程的删除操作,反之亦然。
    • 使用场景及示例 线程池。
      • 线程之间的数据交换
        class DataExchanger {
            private final SynchronousQueue<Data> dataQueue = new SynchronousQueue<>();
        
            class DataProducer extends Thread {
                public void run() {
                    Data data = produceData();
                    dataQueue.put(data);
                }
            }
        
            class DataConsumer extends Thread {
                public void run() {
                    Data data = dataQueue.take();
                    processData(data);
                }
            }
        }
        

  • 📜 DelayQueue:
    • 常用方法
      • offer(E e): 将指定的元素添加到此队列的尾部。
      • take(): 检索并移除此队列的头部,如有必要,等待元素变得可用。
    • 特点
      • 无界。
      • 元素有延迟时间。
    • 结构 基于PriorityQueue
    • 原理 只有当元素的延迟时间到达时,它才能从队列中被获取。
    • 使用场景及示例 任务调度,定时任务。
      • 任务调度
        class TaskScheduler {
            private final DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
        
            public void scheduleTask(DelayedTask task) {
                taskQueue.put(task);
            }
        
            class Worker extends Thread {
                public void run() {
                    while (true) {
                        DelayedTask task = taskQueue.take();
                        task.execute();
                    }
                }
            }
        }
        

4. 📂 Deque (双端队列)

  • 📜 ConcurrentLinkedDeque:
    • 常用方法
      • offerFirst(E e): 在此双端队列的前面插入指定的元素。
      • offerLast(E e): 在此双端队列的末尾插入指定的元素。
      • pollFirst(): 检索并移除此双端队列的第一个元素,如果此双端队列为空,则返回 null。
      • pollLast(): 检索并移除此双端队列的最后一个元素,如果此双端队列为空,则返回 null。
    • 特点
      • 非阻塞。
      • 线程安全。
    • 结构 基于链接节点。
    • 原理 使用无锁算法实现。
    • 使用场景及示例 双端操作的非阻塞队列。
      • 双向任务队列
        class TaskDeque {
            private final ConcurrentLinkedDeque<Task> tasks = new ConcurrentLinkedDeque<>();
        
            public void addTaskToFront(Task task) {
                tasks.offerFirst(task);
            }
        
            public void addTaskToEnd(Task task) {
                tasks.offerLast(task);
            }
        
            public Task fetchTaskFromFront() {
                return tasks.pollFirst();
            }
        
            public Task fetchTaskFromEnd() {
                return tasks.pollLast();
            }
        }
        

  • 📜 LinkedBlockingDeque:
    • 常用方法
      • putFirst(E e): 在此双端队列的前面插入指定的元素,如有必要,等待空间变得可用。
      • putLast(E e): 在此双端队列的末尾插入指定的元素,如有必要,等待空间变得可用。
      • takeFirst(): 检索并移除此双端队列的第一个元素,如有必要,等待元素变得可用。
      • takeLast(): 检索并移除此双端队列的最后一个元素,如有必要,等待元素变得可用。
    • 特点
      • 阻塞。
      • 可选的容量。
    • 结构 基于链接节点。
    • 原理 使用内部锁和条件来实现阻塞操作。
    • 使用场景及示例 双端操作的有界任务队列。
      • 生产者-消费者模型中的双端任务队列
        class ProducerConsumerDequeExample {
            private final LinkedBlockingDeque<Item> deque = new LinkedBlockingDeque<>(10);
        
            class Producer extends Thread {
                public void run() {
                    while (true) {
                        Item item = produceItem();
                        deque.putFirst(item);
                    }
                }
            }
        
            class Consumer extends Thread {
                public void run() {
                    while (true) {
                        Item item = deque.takeLast();
                        consumeItem(item);
                    }
                }
            }
        }
        

5. 📂 Map

  • 📜 ConcurrentHashMap:
    • 常用方法
      • put(K key, V value): 将指定的值与此映射中的指定键关联。
      • get(Object key): 返回与指定键关联的值,如果此映射中不包含该键的映射关系,则返回 null。
      • remove(Object key): 如果存在,则移除此映射中与键关联的映射关系。
    • 特点
      • 高并发。
      • 线程安全。
    • 结构 哈希表。
    • 原理 使用分段锁技术。
    • 使用场景及示例 大规模、高并发的缓存。
      • 缓存实现
        class CacheManager {
            private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
        
            public void putToCache(String key, Object value) {
                cache.put(key, value);
            }
        
            public Object getFromCache(String key) {
                return cache.get(key);
            }
         }
        

  • 📜 ConcurrentSkipListMap:
    • 常用方法
      • put(K key, V value): 将指定的值与此映射中的指定键关联。
      • get(Object key): 返回与指定键关联的值,如果此映射中不包含该键的映射关系,则返回 null。
      • remove(Object key): 如果存在,则移除此映射中与键关联的映射关系。
      • firstKey(): 返回此映射中当前的第一个(最低)键。
      • lastKey(): 返回此映射中当前的最后一个(最高)
    • 特点
      • 线程安全。
      • 提供排序。
    • 结构 跳表。
    • 原理 使用跳表数据结构。
    • 使用场景及示例 需要排序的线程安全的Map。
      • 排行榜应用
        class ScoreboardMap {
            private final ConcurrentSkipListMap<String, Integer> scores = new ConcurrentSkipListMap<>();
        
            public void addScore(String playerName, int score) {
                scores.put(playerName, score);
            }
        
            public int getScore(String playerName) {
                return scores.get(playerName);
            }
        }
        

⚠️ 注意 这些Concurrent集合的设计目标是提供更高的并发性能,特别是在高并发、读多写少的场景中。在使用这些集合时,应考虑它们的特性和适用场景,以确保选择最合适的数据结构。


6. 📂 Atomic


🔵 AtomicBoolean

  • 📍 常用方法

    • get(): 返回当前值。
    • set(boolean newValue): 设置新值。
    • compareAndSet(boolean expect, boolean update): 如果当前值 == 预期值,则原子地设置为新值。
  • 📍 特点

    • 原子更新布尔值。
    • 无锁。
  • 📍 结构 内部使用一个 volatile int 来表示布尔值。

  • 📍 原理 利用 CPU 提供的原子指令来实现原子操作。

  • 📍 使用场景及示例

    • 控制一次性初始化(例如单例模式中的懒加载):

      class Singleton {
          // 使用AtomicBoolean来标记是否已经初始化
          private static final AtomicBoolean isInitialized = new AtomicBoolean(false);
          private static Singleton instance;
          
          public static Singleton getInstance() {
              // 首次检查,提高效率
              if (!isInitialized.get()) {
                  synchronized(Singleton.class) {
                      // 双重检查锁定,确保只初始化一次
                      if (instance == null && !isInitialized.get()) {
                          instance = new Singleton();
                          isInitialized.set(true);
                      }
                  }
              }
              return instance;
          }
      }
      
    • 作为开关或标志来控制多线程中的操作:

      class TaskRunner extends Thread {
          // 使用AtomicBoolean作为运行标志
          private final AtomicBoolean shouldRun = new AtomicBoolean(true);
      
          @Override
          public void run() {
              // 当标志为true时,继续执行任务
              while (shouldRun.get()) {
                  // 执行相关任务...
              }
          }
      
          // 设置标志为false来停止任务
          public void stopTask() {
              shouldRun.set(false);
          }
      }
      

🔵 AtomicInteger

  • 📍 常用方法

    • get()
    • set(int newValue)
    • getAndIncrement()
    • getAndDecrement()
    • compareAndSet(int expect, int update)
  • 📍 特点

    • 原子更新整数值。
    • 无锁。
  • 📍 结构 内部使用一个 volatile int

  • 📍 原理 利用 CPU 提供的原子指令来实现原子操作。

  • 📍 使用场景及示例

    • 作为计数器,例如跟踪在线用户数或监控指标:

      class UserManager {
          // 使用AtomicInteger跟踪在线用户数
          private final AtomicInteger onlineUsersCount = new AtomicInteger(0);
      
          // 用户登录时增加计数
          public void userLoggedIn() {
              onlineUsersCount.incrementAndGet();
          }
      
          // 用户注销时减少计数
          public void userLoggedOut() {
              onlineUsersCount.decrementAndGet();
          }
      
          // 获取当前在线用户数
          public int getOnlineUsersCount() {
              return onlineUsersCount.get();
          }
      }
      
    • 控制资源的访问数量:

      class ResourceLimiter {
          // 使用AtomicInteger跟踪资源的使用数量
          private final AtomicInteger resourceCount = new AtomicInteger(0);
          private static final int LIMIT = 10;
      
          // 尝试使用资源
          public boolean tryUseResource() {
              while (true) {
                  int current = resourceCount.get();
                  if (current >= LIMIT) {
                      return false;  // 资源已达上限
                  }
                  if (resourceCount.compareAndSet(current, current + 1)) {
                      return true;  // 资源使用成功
                  }
              }
          }
          
          // 释放资源
          public void releaseResource() {
              resourceCount.decrementAndGet();
          }
      }
      

🔵 AtomicLong

  • ⚠️ 注意: 和 AtomicInteger 类似,只是它操作的是长整数。
  • 📍 使用场景及示例
    • 作为大数字的计数器:

      class TransactionManager {
          // 使用AtomicLong跟踪交易ID
          private final AtomicLong transactionId = new AtomicLong(0);
      
          // 获取下一个交易ID
          public long getNextTransactionId() {
              // 自增并返回新的交易ID
              return transactionId.incrementAndGet();
          }
      }
      
    • 跟踪系统中的累计值,如总计请求量:

      class RequestMetrics {
          // 使用AtomicLong跟踪系统的总请求数量
          private final AtomicLong totalRequests = new AtomicLong(0);
      
          // 每次请求时增加计数
          public void logRequest() {
              totalRequests.incrementAndGet();
          }
      
          // 获取当前的总请求数量
          public long getTotalRequests() {
              return totalRequests.get();
          }
      }
      

🔵 AtomicReference

  • 📍 常用方法
    • get()
    • set(V newValue)
    • compareAndSet(V expect, V update)
  • 📍 特点
    • 原子更新引用类型。
    • 无锁。
  • 📍 结构 内部使用一个 volatile V 来表示引用。
  • 📍 原理 利用 CPU 提供的原子指令来实现原子操作。
  • 📍 使用场景及示例
    • 在无锁算法中替换对象的引用:

      class ConfigManager<T> {
          // 使用AtomicReference存储配置对象
          private final AtomicReference<T> configReference = new AtomicReference<>();
      
          // 更新配置
          public void updateConfig(T newConfig) {
              configReference.set(newConfig);
          }
      
          // 获取当前的配置
          public T getConfig() {
              return configReference.get();
          }
      }
      
    • 作为缓存策略的一部分,例如跟踪最近的对象:

      class Cache<K, V> {
          private final Map<K, AtomicReference<V>> cacheMap = new ConcurrentHashMap<>();
      
          // 设置缓存项
          public void set(K key, V value) {
              cacheMap.computeIfAbsent(key, k -> new AtomicReference<>()).set(value);
          }
      
          // 获取缓存项
          public V get(K key) {
              AtomicReference<V> ref = cacheMap.get(key);
              return ref == null ? null : ref.get();
          }
      }
      

🔵 AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray

  • ⚠️ 注意: 这些类为数组中的元素提供了原子操作。
  • 它们的常用方法与单个原子类的方法类似,但都带有一个数组索引参数。
  • 📍 使用场景及示例
    • 管理一个固定大小的原子变量集合:
      class Scoreboard {
          // 使用AtomicIntegerArray跟踪每个玩家的得分
          private final AtomicIntegerArray scores;
      
          public Scoreboard(int playerCount) {
              scores = new AtomicIntegerArray(playerCount);
          }
      
          // 增加玩家的得分
          public void addScore(int playerIndex, int delta) {
              scores.addAndGet(playerIndex, delta);
          }
      
          // 获取玩家的得分
          public int getScore(int playerIndex) {
              return scores.get(playerIndex);
          }
      }
      

🔵 AtomicMarkableReference

  • 📍 常用方法
    • get(): 返回当前引用和标记。
    • set(V newReference, boolean newMark): 设置新引用和标记。
    • compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark): 如果当前引用和标记与预期的引用和标记匹配,则原子地设置新引用和标记。
  • 📍 使用场景及示例
    • 解决 ABA 问题:
      class Stack<T> {
          private static class Node<T> {
              final T item;
              Node<T> next;
              Node(T item) { this.item = item; }
          }
      
          private final AtomicStampedReference<Node<T>> top = new AtomicStampedReference<>(null, 0);
      
          public void push(T item) {
              Node<T> newNode = new Node<>(item);
              int[] stampHolder = new int[1];
              while (true) {
                  Node<T> currentTop = top.get(stampHolder);
                  newNode.next = currentTop;
                  if (top.compareAndSet(currentTop, newNode, stampHolder[0], stampHolder[0] + 1)) {
                      break;
                  }
              }
          }
      
          public T pop() {
              int[] stampHolder = new int[1];
              while (true) {
                  Node<T> currentTop = top.get(stampHolder);
                  if (currentTop == null) return null;
                  if (top.compareAndSet(currentTop, currentTop.next, stampHolder[0], stampHolder[0] + 1)) {
                      return currentTop.item;
                  }
              }
          }
      }
      

🔵 AtomicStampedReference

  • ⚠️ 注意: 和 AtomicMarkableReference 类似,只是它的标记是一个整数,而不是一个布尔值。

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yueerba126

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值