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. Python3.7+Robot Framework+RIDE1.7.4.1安装使用教程

    一.解惑:Robot Framewprk今天我们聊一聊,Robot Framework被众多测试工程师误会多年的秘密.今天我们一起来揭秘一下,最近经常在各大群里听到许多同行,在拿Robot Frame ...

  2. 安装Ingress-Nginx

    目前,DHorse(https://gitee.com/i512team/dhorse)只支持Ingress-nginx的Ingress实现,下面介绍Ingress-nginx的安装过程. 下载安装文 ...

  3. WPF 自定义泛型用户控件后跨程序集继承用户控件的解决方案

    自定义泛型用户控件: <UserControl x:Class="ClassLibrary1.UcEumCmb" xmlns="http://schemas.mic ...

  4. Fedora升级33->34

    Fedora升级33->34   1. dnf --refresh upgrade   2. dnf install dnf-plugin-system-upgrade --best   3. ...

  5. 【FAQ】HarmonyOS SDK 闭源开放能力 —Account Kit(2)

    1.问题描述: 怎么判断登录的华为帐号有变动? 解决方案: 华为帐号登录成功后会返回唯一标识OpenID和UnionID,如果切换不同的华为帐号登录,这个唯一标识会变. OpenID是华为帐号用户在不 ...

  6. arm linux 移植 i2c-tools 与 简单使用

    介绍 i2c-tool是一个专门调试i2c的开源工具.可获取挂载的设备及设备地址,还可以在对应的设备指定寄存器设置值或者获取值等功能,对于驱动以及应用开发者比较友好. i2c-tool:v3.0.3 ...

  7. 【点云检测】OpenPCDet 教程系列 [1] 安装 与 ROS运行

    前言与参考 主要是介绍库的使用,做笔记区 首先搜索的时候有个问题 一直在我脑子里 hhh 就是MMlab其实还有一个叫mmdetection3d 的库,然后搜的时候发现 hhh 有网友和我一样的疑惑: ...

  8. 一文搞懂到底什么是 AQS

    前言 日常开发中,我们经常使用锁或者其他同步器来控制并发,那么它们的基础框架是什么呢?如何实现的同步功能呢?本文将详细讲解构建锁和同步器的基础框架--AQS,并根据源码分析其原理. 一.什么是 AQS ...

  9. win10彻底关闭windows defender,解决无故占用大量CPU问题

    win10彻底关闭defender的方法 首先右键开始菜单按钮,点击"运行",输入"gpedit.msc",打开"本地组策略编辑器". 依次 ...

  10. P2935

    [USACO09JAN]Best Spot S 题目 约翰拥有P(1<=P<=500)个牧场.贝茜特别喜欢其中的F个.所有的牧场 由C(1 < C<=8000)条双向路连接,第 ...