java多线程之-CAS无锁
1.背景
加锁确实能解决线程并发的的问题,但是会造成线程阻塞等待等问题
那么有没有一种方法,既可以线程安全,又不会造成线程阻塞呢?
答案是肯定的......请看如下案例
注意:重要的文字说明,写在了代码注释上,这样便于大家理解,请阅读代码和注释加以理解;
2.取钱案例引出问题
启动10000个线程,每个线程减去10元
原来账户共10000 0元
正常情况账户最后的余额应该是0元
测试多线程并发问题
先定义一个通用的接口,后面使用不同实现来测试
账户Money接口:

package com.ldp.demo05; import java.util.ArrayList;
import java.util.List; /**
* @author 姿势帝-博客园
* @address https://www.cnblogs.com/newAndHui/
* @WeChat 851298348
* @create 02/16 8:14
* @description
*/
public interface Money {
// 获取余额
Integer getBalance(); // 取款
void reduce(Integer amount); /**
* 启动10000个线程,每个线程减去10元
* 原来账户共10000 0元
* 正常情况应该是0元
* 测试多线程并发问题
*
* @param account
*/
static void handel(Money account) {
List<Thread> ts = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < 10000; i++) {
ts.add(new Thread(() -> {
account.reduce(10);
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println("当前余额:" + account.getBalance()
+ " ,耗时: " + (end - start) / 1000_000 + " ms");
}
}
2.1.存在线程安全的解决方案

package com.ldp.demo05.impl; import com.ldp.demo05.Money; /**
* @author 姿势帝-博客园
* @address https://www.cnblogs.com/newAndHui/
* @WeChat 851298348
* @create 02/16 8:17
* @description
*/
public class UnSafeMoney implements Money {
private Integer balance; public UnSafeMoney(Integer balance) {
this.balance = balance;
} @Override
public Integer getBalance() {
return balance;
} @Override
public void reduce(Integer amount) {
// 存在线程不安全
balance -= amount;
}
}
2.2.使用传统锁的解决方案

package com.ldp.demo05.impl; import com.ldp.demo05.Money; /**
* @author 姿势帝-博客园
* @address https://www.cnblogs.com/newAndHui/
* @WeChat 851298348
* @create 02/16 8:17
* @description
*/
public class SyncSafeMoney implements Money {
private Integer balance; public SyncSafeMoney(Integer balance) {
this.balance = balance;
} @Override
public Integer getBalance() {
return balance;
} @Override
public synchronized void reduce(Integer amount) {
// 当前对象加锁 安全
balance -= amount;
}
}
2.3.使用CAS无锁的解决方案

package com.ldp.demo05.impl; import com.ldp.demo05.Money; import java.util.concurrent.atomic.AtomicInteger; /**
* @author 姿势帝-博客园
* @address https://www.cnblogs.com/newAndHui/
* @WeChat 851298348
* @create 02/16 8:30
* @description <p>
* 无锁的思路
*
* </p>
*/
public class CASSafeMoney implements Money {
private AtomicInteger balance; public CASSafeMoney(Integer balance) {
this.balance = new AtomicInteger(balance);
} @Override
public Integer getBalance() {
return balance.get();
} /**
* compareAndSet 做这个检查,在 set 前,先比较 prev 与当前值不一致了,next 作废,返回 false 表示失败
* <p>
* 其实 CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交换】的原子性。
* 在多核状态下,某个核执行到带 lock 的指令时,CPU 会让总线锁住,当这个核把此指令执行完毕,再
* 开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。
* <p>
* CAS 的特点
* 结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。
* 1.CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,重试在执行【修改】。
* 2.synchronized 是基于悲观锁的思想:最悲观的估计,防着其它线程来修改共享变量,当前线程上锁后,其他线程阻塞等待。
* 3.CAS 体现的是【无锁并发、无阻塞并发】。
* 因为没有使用 synchronized,所以线程【不会陷入阻塞】,这是效率提升的因素之一,
* 但如果竞争激烈,可以想到重试必然频繁发生,反而频繁切换上下文,效率会受影响。
* 4.特别注意:
* 无锁情况下,但如果竞争激烈,因为线程要保持运行,需要CPU 的支持,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
*
* @param amount
*/
@Override
public void reduce(Integer amount) {
// 不断尝试直到成功为止
while (true) {
// 修改前的值
int prev = balance.get();
// 修改后的值
int next = prev - amount;
// 执行修改 compareAndSet 使用CAS乐观锁实现
if (balance.compareAndSet(prev, next)) {
break;
}
}
// 简要写法
// balance.addAndGet(-1 * amount);
}
}
3.测试

package com.ldp.demo05; import com.ldp.demo05.impl.CASSafeMoney; /**
* @author 姿势帝-博客园
* @address https://www.cnblogs.com/newAndHui/
* @WeChat 851298348
* @create 02/16 8:20
* @description
*/
public class Test01Money {
public static void main(String[] args) {
// 1.无锁不安全 当前余额:29530 ,耗时: 4847 ms
// Money.handel(new UnSafeMoney(100000)); // 2.synchronized加锁安全 当前余额:0 ,耗时: 7386 ms
// Money.handel(new SyncSafeMoney(100000)); // 3.使用乐观锁 CAS 当前余额:0 ,耗时: 3466 ms
Money.handel(new CASSafeMoney(100000));
}
}
4.CompareAndSet 方法分析

package com.ldp.demo05; import com.common.MyThreadUtil;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicInteger; /**
* @author 姿势帝-博客园
* @address https://www.cnblogs.com/newAndHui/
* @WeChat 851298348
* @create 02/16 8:49
* @description
*/
@Slf4j
public class Test02CompareAndSet {
/**
* 观察多线程修改值
*
* @param args
*/
public static void main(String[] args) {
AtomicInteger n = new AtomicInteger(100);
int mainPrev = n.get();
log.info("当前值:{}", n.get());
// 线程 t1 将其修改为 90
new Thread(() -> {
// 模拟睡眠3秒
MyThreadUtil.sleep(1);
boolean b = n.compareAndSet(mainPrev, 90);
log.info("修改结果:{}", b);
}, "t1").start(); // 模拟睡眠3秒
MyThreadUtil.sleep(2);
new Thread(() -> {
boolean b = n.compareAndSet(mainPrev, 80);
log.info("修改结果:{}", b);
}, "t2").start();
// 最后结果值
MyThreadUtil.sleep(2);
log.info("最后值为={}", n.get());
// 21:04:15.369 [main] -> 当前值:100
// 21:04:16.451 [t1] -> 修改结果:true
// 21:04:17.457 [t2] -> 修改结果:false
// 21:04:19.457 [main] -> 最后值为=90
}
}
完美!
java多线程之-CAS无锁的更多相关文章
- java 多线程12 : 无锁 实现CAS原子性操作----原子类
由于java 多线程11:volatile关键字该文讲道可以使用不带锁的情况也就是无锁使变量变成可见,这里就理解下如何在无锁的情况对线程变量进行CAS原子性及可见性操作 我们知道,在并发的环境下,要实 ...
- java并发:AtomicInteger 以及CAS无锁算法【转载】
1 AtomicInteger解析 众所周知,在多线程并发的情况下,对于成员变量,可能是线程不安全的: 一个很简单的例子,假设我存在两个线程,让一个整数自增1000次,那么最终的值应该是1000:但是 ...
- (转载)java高并发:CAS无锁原理及广泛应用
java高并发:CAS无锁原理及广泛应用 版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处. 博主博客地址是 http://blog.csdn.net/liubenlong007 ...
- CAS无锁算法与ConcurrentLinkedQueue
CAS:Compare and Swap 比较并交换 java.util.concurrent包完全建立在CAS之上的,没有CAS就没有并发包.并发包借助了CAS无锁算法实现了区别于synchroni ...
- CAS无锁机制原理
原子类 java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程 原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读 ...
- Java多线程专题5: JUC, 锁
合集目录 Java多线程专题5: JUC, 锁 什么是可重入锁.公平锁.非公平锁.独占锁.共享锁 可重入锁 ReentrantLock A ReentrantLock is owned by the ...
- Java多线程6:Synchronized锁代码块(this和任意对象)
一.Synchronized(this)锁代码块 用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待.这种情况下就 ...
- Java多线程5:Synchronized锁机制
一.前言 在多线程中,有时会出现多个线程对同一个对象的变量进行并发访问的情形,如果不做正确的同步处理,那么产生的后果就是“脏读”,也就是获取到的数据其实是被修改过的. 二.引入Synchronized ...
- java多线程--6 死锁问题 锁Lock
java多线程--6 死锁问题 锁Lock 死锁问题 多个线程互相抱着对方需要的资源,然后形成僵持 死锁状态 package com.ssl.demo05; public class DeadLock ...
- Java高并发之无锁与Atomic源码分析
目录 CAS原理 AtomicInteger Unsafe AtomicReference AtomicStampedReference AtomicIntegerArray AtomicIntege ...
随机推荐
- Python3.7+Robot Framework+RIDE1.7.4.1安装使用教程
一.解惑:Robot Framewprk今天我们聊一聊,Robot Framework被众多测试工程师误会多年的秘密.今天我们一起来揭秘一下,最近经常在各大群里听到许多同行,在拿Robot Frame ...
- 安装Ingress-Nginx
目前,DHorse(https://gitee.com/i512team/dhorse)只支持Ingress-nginx的Ingress实现,下面介绍Ingress-nginx的安装过程. 下载安装文 ...
- WPF 自定义泛型用户控件后跨程序集继承用户控件的解决方案
自定义泛型用户控件: <UserControl x:Class="ClassLibrary1.UcEumCmb" xmlns="http://schemas.mic ...
- Fedora升级33->34
Fedora升级33->34 1. dnf --refresh upgrade 2. dnf install dnf-plugin-system-upgrade --best 3. ...
- 【FAQ】HarmonyOS SDK 闭源开放能力 —Account Kit(2)
1.问题描述: 怎么判断登录的华为帐号有变动? 解决方案: 华为帐号登录成功后会返回唯一标识OpenID和UnionID,如果切换不同的华为帐号登录,这个唯一标识会变. OpenID是华为帐号用户在不 ...
- arm linux 移植 i2c-tools 与 简单使用
介绍 i2c-tool是一个专门调试i2c的开源工具.可获取挂载的设备及设备地址,还可以在对应的设备指定寄存器设置值或者获取值等功能,对于驱动以及应用开发者比较友好. i2c-tool:v3.0.3 ...
- 【点云检测】OpenPCDet 教程系列 [1] 安装 与 ROS运行
前言与参考 主要是介绍库的使用,做笔记区 首先搜索的时候有个问题 一直在我脑子里 hhh 就是MMlab其实还有一个叫mmdetection3d 的库,然后搜的时候发现 hhh 有网友和我一样的疑惑: ...
- 一文搞懂到底什么是 AQS
前言 日常开发中,我们经常使用锁或者其他同步器来控制并发,那么它们的基础框架是什么呢?如何实现的同步功能呢?本文将详细讲解构建锁和同步器的基础框架--AQS,并根据源码分析其原理. 一.什么是 AQS ...
- win10彻底关闭windows defender,解决无故占用大量CPU问题
win10彻底关闭defender的方法 首先右键开始菜单按钮,点击"运行",输入"gpedit.msc",打开"本地组策略编辑器". 依次 ...
- P2935
[USACO09JAN]Best Spot S 题目 约翰拥有P(1<=P<=500)个牧场.贝茜特别喜欢其中的F个.所有的牧场 由C(1 < C<=8000)条双向路连接,第 ...