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无锁的更多相关文章

  1. java 多线程12 : 无锁 实现CAS原子性操作----原子类

    由于java 多线程11:volatile关键字该文讲道可以使用不带锁的情况也就是无锁使变量变成可见,这里就理解下如何在无锁的情况对线程变量进行CAS原子性及可见性操作 我们知道,在并发的环境下,要实 ...

  2. java并发:AtomicInteger 以及CAS无锁算法【转载】

    1 AtomicInteger解析 众所周知,在多线程并发的情况下,对于成员变量,可能是线程不安全的: 一个很简单的例子,假设我存在两个线程,让一个整数自增1000次,那么最终的值应该是1000:但是 ...

  3. (转载)java高并发:CAS无锁原理及广泛应用

    java高并发:CAS无锁原理及广泛应用   版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处. 博主博客地址是 http://blog.csdn.net/liubenlong007 ...

  4. CAS无锁算法与ConcurrentLinkedQueue

    CAS:Compare and Swap 比较并交换 java.util.concurrent包完全建立在CAS之上的,没有CAS就没有并发包.并发包借助了CAS无锁算法实现了区别于synchroni ...

  5. CAS无锁机制原理

    原子类 java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程 原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读 ...

  6. Java多线程专题5: JUC, 锁

    合集目录 Java多线程专题5: JUC, 锁 什么是可重入锁.公平锁.非公平锁.独占锁.共享锁 可重入锁 ReentrantLock A ReentrantLock is owned by the ...

  7. Java多线程6:Synchronized锁代码块(this和任意对象)

    一.Synchronized(this)锁代码块 用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待.这种情况下就 ...

  8. Java多线程5:Synchronized锁机制

    一.前言 在多线程中,有时会出现多个线程对同一个对象的变量进行并发访问的情形,如果不做正确的同步处理,那么产生的后果就是“脏读”,也就是获取到的数据其实是被修改过的. 二.引入Synchronized ...

  9. java多线程--6 死锁问题 锁Lock

    java多线程--6 死锁问题 锁Lock 死锁问题 多个线程互相抱着对方需要的资源,然后形成僵持 死锁状态 package com.ssl.demo05; public class DeadLock ...

  10. Java高并发之无锁与Atomic源码分析

    目录 CAS原理 AtomicInteger Unsafe AtomicReference AtomicStampedReference AtomicIntegerArray AtomicIntege ...

随机推荐

  1. Mybatis 动态 sql 是做什么的?都有哪些动态 sql?能简述一下动态 sql 的执行原理不?

    a.Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能. b.Mybatis 提 供 了 9 种 动 态 sql 标 ...

  2. javascript 类class设置访问器setter时出现Maximum call stack size exceeded错误

    Maximum call stack size exceeded这个错误的意思是调用栈溢出,但是自己写的代码基本不可能出现.所以可能的原因是A调用了B,然后B再调用A,形成了循环调用.或者说是A自己调 ...

  3. Java 对象转Map,Java Map转对象方法

    Java 对象转Map,Java Map转对象方法 import com.alibaba.fastjson.JSON; import org.apache.commons.beanutils.Bean ...

  4. 常用RAID级别简介

    RAID不同等级的两个目标: 1. 增加数据可靠性 2. 增加存储的读写性能 RAID级别: ​ RAID-0: 是以条带的形式将数据均匀分布在阵列的各个磁盘上 ​ 优点:读写性能高,不存在校验,不会 ...

  5. vim中ctags 的使用

    --- title: vim中ctags 的使用 EntryName: vim-config-with-ctags date: 2020-08-19 11:17:38 categories: tags ...

  6. c语言之位段

    百度百科链接 示例: 1 struct CHAR 2 { 3 unsigned int ch : 8; //8位 4 unsigned int font : 6; //6位 5 unsigned in ...

  7. Window版 MySQL可视化工具 Navicat 面安装免激活绿色版

    网盘地址 链接:https://pan.baidu.com/s/1T0WyhGAFEt28GaU4wXhfrg 提取码:z4ww navicat15破解版 链接:https://pan.baidu.c ...

  8. 面试官:Dubbo一次RPC请求经历哪些环节?

    大家好,我是三友~~ 今天继续探秘系列,扒一扒一次RPC请求在Dubbo中经历的核心流程. 本文是基于Dubbo3.x版本进行讲解 一个简单的Demo 这里还是老样子,为了保证文章的完整性和连贯性,方 ...

  9. Mysql-explain之Using temporary和Using filesort解决方案

    第一条语句 explainselect * from tb_wm_shop where is_delete != 1 and is_authentication = 1 ORDER BY create ...

  10. Element-plus的徽章组件el-badge

    Element-plus的徽章组件el-badge Element Plus 是一个基于 Vue.js 的 UI 组件库,它提供了一系列的常用 UI 组件供开发者使用.其中,徽章组件(el-badge ...