
目录
1.前言
2.正文
2.1CAS概念
2.2CAS两种用途
2.2.1实现原子类
2.2.2实现自旋锁
2.3缺陷:ABA问题
2.4JUC组件
2.4.1Callable接口
2.4.2ReentrantLock(与synchronized对比)
2.4.3Semaphore信号量
2.4.4CountDownLatch
3.小结
1.前言
哈喽大家好吖,不知不觉多线程这一块大骨头终于快要啃完了,今天给大家分享的是CAS以及JUC相关组件,那么废话不多说让我们开始吧。
2.正文
2.1CAS概念
核心思想:无所并发控制
CAS(Compare And Swap)是一种基于乐观锁的无锁并发控制技术。其核心逻辑可以概括为:“我认为当前值应该是A,如果是,则更新为B;否则放弃或重试”。整个过程由硬件保证原子性,无需传统锁机制。
通俗来说
假设你和同事协同编辑一份共享文档,每次保存时系统会检查:
当前内容是否和你打开时的版本一致(预期值比对)。
如果一致,允许保存;否则提示“内容已变更,请重新编辑”。
这个过程就是CAS的核心思想——乐观锁:先操作,冲突时重试,而非直接加锁阻塞。
CAS操作的伪代码可以拆解为以下步骤,帮助理解其原子性本质:
// 伪代码:CAS操作的逻辑分解
public boolean compareAndSwap(MemoryAddress addr, int expectedValue, int newValue) {// 1. 读取内存当前值int currentValue = *addr; // 2. 比较当前值与预期值if (currentValue != expectedValue) {return false; // 值已被其他线程修改,操作失败}// 3. 若值未变,执行原子性更新*addr = newValue;return true;
}2.2CAS两种用途
2.2.1实现原子类
针对原子类,++--这样的操作是原子的,基于CAS实现,不涉及到加锁。
传统实现:
private int count = 0;  
public synchronized void increment() {  count++;  
}  进阶实现: (使用Java提供的原子类)
AtomicInteger count = new AtomicInteger(0);  
public void increment() {  int oldValue, newValue;  do {  oldValue = count.get();  newValue = oldValue + 1;  } while (!count.compareAndSet(oldValue, newValue)); // CAS自旋  
}  2.2.2实现自旋锁
先回顾一个上篇文章的概念:自旋锁是线程通过循环(自旋)不断尝试获取锁,而非立即阻塞。适用于锁持有时间极短的场景。
代码实现:
public class CASSpinLock {  private AtomicBoolean locked = new AtomicBoolean(false);  // 获取锁  public void lock() {  while (!locked.compareAndSet(false, true)) {  // 自旋:直到成功将locked从false改为true  }  }  // 释放锁  public void unlock() {  locked.set(false);  }  
}  
线程竞争不激烈时(如短任务),自旋锁比系统锁(如
synchronized)更高效。
缺点:长时间自旋会浪费CPU资源(需根据场景权衡)。
2.3缺陷:ABA问题
ABA问题场景
-  线程1读取变量值为 A。
-  线程2将值改为 B,随后又改回A。
-  线程1执行CAS操作,发现当前值仍是 A,误认为未被修改过,操作成功。
通俗理解:
你看到自己的水杯是满的(A),去接水时离开了一会儿。
期间别人喝光水(A→B)又倒满(B→A)。
你回来后以为水没被喝过,直接喝下(可能喝到别人的水!)。
这里在实际场景中就是非常严重的线程安全的问题了。
解决方案:
1. 版本号标记(AtomicStampedReference)
为值附加一个版本号(类似“修改次数”),CAS时同时校验值和版本号。AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0); // 线程1读取值和版本号 int stamp = ref.getStamp(); String oldValue = ref.getReference(); // 线程2修改值并更新版本号 ref.compareAndSet("A", "B", stamp, stamp + 1); ref.compareAndSet("B", "A", stamp + 1, stamp + 2); // 线程1尝试修改:虽然值还是A,但版本号已变,操作失败! boolean success = ref.compareAndSet(oldValue, "C", stamp, stamp + 1);2. 状态标记(AtomicMarkableReference)
用布尔值标记是否被修改过(简化版版本号)。
2.4JUC组件
2.4.1Callable接口
官方解析:Callable (Java SE 17 & JDK 17)
Callable是 Java 并发包(JUC)中定义的接口,类似于Runnable,但允许线程执行任务后返回结果,并可以抛出异常。与
Runnable的区别:
Runnable的run()没有返回值,Callable的call()可以返回泛型结果。
call()可以抛出受检异常,run()不能。
具体案例(异步运算1加到100):
Callable<Integer> task = () -> {int sum = 0;for (int i = 1; i <= 100; i++) sum += i;return sum;
};
FutureTask<Integer> futureTask = new FutureTask<>(task);
new Thread(futureTask).start();// 主线程获取结果
System.out.println("计算结果:" + futureTask.get()); // 输出 5050通过
FutureTask包装Callable任务,启动线程执行后,主线程通过futureTask.get()等待结果返回,类似“异步任务+回调”模式。
2.4.2ReentrantLock(与synchronized对比)
官方解析:ReentrantLock (Java SE 17 & JDK 17)
ReentrantLock 是 JUC 提供的显式锁,支持可重入性、可中断锁、公平锁等特性。
| 特性 | synchronized | ReentrantLock | 
|---|---|---|
| 锁获取方式 | 隐式(JVM 管理) | 显式(代码手动加锁/解锁) | 
| 可中断 | 不支持 | 支持 lockInterruptibly() | 
| 公平锁 | 不支持 | 支持(构造函数指定) | 
| 条件变量(Condition) | 无 | 支持( newCondition()) | 
案例:
class BankAccount {private final ReentrantLock lock = new ReentrantLock();private int balance = 100;void transfer(BankAccount target, int amount) {lock.lock();try {if (this.balance >= amount) {this.balance -= amount;target.balance += amount;}} finally {lock.unlock(); // 必须手动释放锁}}
}
synchronized的等价实现是在方法签名加synchronized关键字,但ReentrantLock更灵活:
可设置超时时间(
tryLock(1, TimeUnit.SECONDS))。
公平锁减少线程饥饿问题。
2.4.3Semaphore信号量
官方解析:Semaphore (Java SE 17 & JDK 17)
Semaphore 用于控制同时访问某个资源的线程数量,类似“许可证发放”。
核心方法:
acquire():获取许可证(若无可用则阻塞)。
release():释放许可证。
案例:(模拟停车场)
Semaphore semaphore = new Semaphore(3); // 3 个许可证Runnable parkAction = () -> {try {semaphore.acquire(); // 获取车位System.out.println(Thread.currentThread().getName() + " 停入车位");Thread.sleep(2000); // 停车 2 秒} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release(); // 释放车位System.out.println(Thread.currentThread().getName() + " 离开车位");}
};// 启动 5 辆车尝试停车
for (int i = 0; i < 5; i++) {new Thread(parkAction).start();
}
2.4.4CountDownLatch
官方解析:CountDownLatch (Java SE 17 & JDK 17)
CountDownLatch 是一个同步工具,允许一个或多个线程等待其他线程完成操作。
核心方法:
countDown():计数器减 1。
await():阻塞直到计数器归零。
public static void main(String[] args) {CountDownLatch latch = new CountDownLatch(3); // 需要等待 3 个任务// 资源加载任务Runnable loadTask = () -> {try {Thread.sleep((long) (Math.random() * 2000));System.out.println(Thread.currentThread().getName() + " 加载完成");latch.countDown();} catch (InterruptedException e) {e.printStackTrace();}};// 启动 3 个资源加载线程new Thread(loadTask, "地图").start();new Thread(loadTask, "音效").start();new Thread(loadTask, "UI").start();// 主线程等待所有资源加载完成try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("所有资源加载完成,开始游戏!");}
3.小结
今天的分享到这里就结束了,喜欢的小伙伴点点赞点点关注,你的支持就是对我最大的鼓励,大家加油!