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 ...
随机推荐
- cuda性能优化-2.访存优化
简介 在CUDA程序中, 访存优化个人认为是最重要的优化项. 往往kernel会卡在数据传输而不是计算上, 为了最大限度利用GPU的计算能力, 我们需要根据GPU硬件架构对kernel访存进行合理的编 ...
- redhat6.5 升级 openssl
上传版本包: openssl-1.0.1u.tar.gz 解压缩安装包: tar -xf openssl-1.0.1u.tar.gz 安装版本: ./config --prefix=/usr/loca ...
- spring之NamedParameterJdbcTemplate返回自增列值
以前使用JdbcTemplate来获取自增列的值,现在发现NamedParameterJdbcTemplate也可以,而且后者大部分情况下,其实更加方便. 这种方便主要是在于代码维护方面:我们更加习惯 ...
- admission-controllers
WebHook是什么 官方文档: https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controller ...
- MySQL自定义函数(User Define Function)开发实例——发送TCP/UDP消息
开发背景 当数据库中某个字段的值改为特定值时,实时发送消息通知到其他系统. 实现思路 监控数据库中特定字段值的变化可以用数据库触发器实现.还需要实现一个自定义的函数,接收一个字符串参数,然后将这个字符 ...
- SpringMVC-01-回顾MVC架构
1.什么是MVC MVC是模型(Model).视图(View).控制器(Controller)的简写,是一种软件架构模式. 它通过将业务逻辑.页面控制.显示视图分离的方法来组织代码. 主要作用是降低了 ...
- 使用Github Action来辅助项目管理
Github action 是一个Github官方提供的非常流行且速度集成 持续集成和持续交付(CI/CD)的工具.它允许你在GitHub仓库中自动化.定制和执行你的软件开发工作流.你可以发现.创建和 ...
- 创龙科技位居头版,2023深圳elexcon电子展为智能化赋能!
紧跟前沿技术应用及市场发展热点,elexcon2023聚焦三大展示板块:"嵌入式与AIoT展""电源与储能展""SiP与先进封装展",吸引了 ...
- Power BI实用技巧:轻松打造专业级甘特图
Power BI实用技巧:轻松打造专业级甘特图 大家好,今天我们要一起探索Power BI中一个既实用又强大的功能--制作甘特图.甘特图以其直观展示项目时间线和任务进度的特点,在项目管理中扮演着重要角 ...
- day02模板与配置
一.WXML模板语法 1.1 数据绑定 绑定内容 跟vue差不多,在页面的js文件定义到data里面 然后通过插值语法用在wxml中即可 绑定属性 直接写上插值语法,没有: 三元运算 生成一个十以内的 ...