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 ...
随机推荐
- mysql删除主键索引,删除索引语法
mysql删除主键索引,删除索引语法 ### Incorrect table definition; there can be only one auto column and it must be ...
- spring mvc GET请求方式及传参
spring mvc GET请求方式及传参 @Api(tags = "管理接口") @Slf4j @RestController @RequestMapping("/my ...
- RHEL8.1---离线升级gcc
升级gcc到gcc9.1.0 下载离线包.放到/opt下 [root@172-18-251-35 opt]# wget http://ftp.gnu.org/gnu/gcc/gcc-9.1.0/gcc ...
- HDOJ 6703 Array
HDOJ 6703 Array 题目 题目链接 array *Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K ...
- IoTBrowser V2.0:引领物联网时代的全新浏览器
强大的兼容性,无限的可能 IoTBrowser V2.0,基于Chromium内核,完美支持H5/css/js开发界面,让您的物联网应用拥有与主流浏览器同等的流畅体验.同时,它还支持CSS 3动画.C ...
- Netty(一)IO模型
1. Netty介绍 Netty 是由JBOSS提供的一个Jave开源框架,是一个异步地.基于事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络IO程序. Netty主要针对在TCP协议下,面向 ...
- 单qubit量子门
量子编程的基本单元就是量子门.量子编程有点像传统的电路设计,一个量子程序可以被写成量子门序列. 图中有一些符合,比如H门.X门.Z门.测量等,我们都会接触到. 传统计算机程序的输入和输出可以不一样,但 ...
- Java使用不同方式优雅拆分业务逻辑
如何处理复杂的业务逻辑 在实际的业务开发当中,经常会遇到复杂的业务逻辑,可能实现出来的代码并没有什么问题,但是代码的可读性很差. 那么在实际开发中如何避免大面积的 if-else 代码块的问题? 补充 ...
- Git 奇幻之旅⌛️续集
第十二天:暂存未完成的修改 小明和小红在开发一个新功能时,他们需要切换到另一个分支去修复一个紧急的 bug .但是他们的当前分支上还有一些未完成的修改,他们不想提交这些修改,也不想丢弃这些修改.有一天 ...
- oeasy教您玩转vim - 84 - # 命令command
命令 command 回忆 关于 函数function 可以调用别的函数 :call append(0,"oeasy o2z o3z") 还可以执行表达式 :call exec ...