首页 > 其他分享 >并发学习记录10:共享模型之无锁

并发学习记录10:共享模型之无锁

时间:2022-09-04 13:45:57浏览次数:77  
标签:10 Account 之无锁 并发 amount 线程 Integer balance public

一个小例子引入

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

interface Account {
    Integer getBalance();

    void withdraw(Integer amount);

    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(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() + "\ntime cost:" +
                (end - start) / 1000 + "ms");
    }
}

//线程不安全的版本,多线程执行取款操作会出现问题
class AccountUnsafe implements Account {
    private Integer balance;

    public AccountUnsafe(Integer balance) {
        this.balance = balance;
    }

    @Override
    public Integer getBalance() {
        return balance;
    }

    @Override
    public void withdraw(Integer amount) {
        balance = balance - amount;
    }
}

//用锁实现的线程安全的版本
class AccountSafe implements Account {
    private Integer balance;

    public AccountSafe(Integer balance) {
        this.balance = balance;
    }

    @Override
    public Integer getBalance() {
        return balance;
    }

    @Override
    public void withdraw(Integer amount) {
        synchronized (this) {
            balance = balance - amount;
        }
    }
}

//用cas实现的线程安全的版本,compare and set
class AccountSafe01 implements Account {
    private AtomicInteger balance;

    public AccountSafe01(Integer balance) {
        this.balance = new AtomicInteger(balance);
    }

    @Override
    public Integer getBalance() {
        return balance.get();
    }

    @Override
    public void withdraw(Integer amount) {
        while (true) {
            int prev = balance.get();
            int next = prev - amount;
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
    }
}

public class TestAccount {
    public static void main(String[] args) {
//        Account.demo(new AccountUnsafe(10000));
//        Account.demo(new AccountSafe(10000));
        Account.demo(new AccountSafe01(10000));
    }
}

以上是一个模拟银行取款的测试,分别用synchronized和cas无锁机制实现了线程安全的操作。

cas和volatile

withdraw的cas方法实现:

public void withdraw(Integer amount) {
 while(true) {
 // 需要不断尝试,直到成功为止
 while (true) {
 // 比如拿到了旧值 1000
 int prev = balance.get();
 // 在这个基础上 1000-10 = 990
 int next = prev - amount;
 /*
 compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值
 - 不一致了,next 作废,返回 false 表示失败
 比如,别的线程已经做了减法,当前值已经被减成了 990
 那么本线程的这次 990 就作废了,进入 while 下次循环重试
 - 一致,以 next 设置为新值,返回 true 表示成功
 */
if (balance.compareAndSet(prev, next)) {
 break;
 }
 }
 }
}

最关键的操作就是compareAndSet操作,简称CAS,CAS底层有机制保证它一定是原子性的

volatile
获取共享变量时,为了保证共享变量的可见性,需要用volatile来修饰
它可以用来修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。即另一个线程对volatile变量的修改,对另一个线程可见
volatile仅仅保证了共享变量的可见性,让其他线程能看到最新值,但并不能解决指令交错的问题
CAS必须借助volatile才能读取到共享变量的最新值来实现比较并交换的效果。

为什么无锁效率高?

无锁情况下,即使重试失败,线程也在告诉运行没有停歇,而synchronized会让线程在获得锁的时候发生上下文切换,进入阻塞。

CAS的特点

结合CAS和volatile可以实现无锁并发,适用于线程数少,多核CPU的场景下
CAS是基于乐观锁的思想,最乐观的估计,就算别的线程来修改了我的共享变量,我也可以识别读取并再修改一次

synchronized是基于悲观锁的思想,最悲观的估计,需要防着其他线程来修改共享变量,只有等我这个线程修改完了这个共享变量解锁之后,其他线程才能来修改

CAS体现的是无锁并发,无阻塞并发。但是如果竞争非常激烈,导致修改的重试频繁发生,反而效率会受到影响

标签:10,Account,之无锁,并发,amount,线程,Integer,balance,public
From: https://www.cnblogs.com/wbstudy/p/16654952.html

相关文章

  • GYM100851 F - Froggy Ford(最短路铜牌题)
    题意:​ 现在有一条河,河中有n个石头,你需要从河的一端到河的另一端。现在你有一次机会在任意位置放置一个石头,请问石头放在哪里可以使过河的最长路径最短。请输出放置的石头......
  • 10 个实用的 Python 编程技巧
    字典翻转首先我们来看字典的翻转,假设我们有下面类似的一个字典对象与数据car_dict={"brand":"Tesla","model":"ModelY","year":2017}倘若我们......
  • [数据结构10分钟入门] 面向初学者从零实现(基于C语言)-- 单链表
    ​一、链表是什么    链表是一种通过指针串联在一起的线性结构,在内存中是分散存储的(数组在内存中连续分布),链表由一系列节点组成,每个节点都由数据域和指针域组成。主......
  • 并发学习记录09:共享模型之内存
    Java内存模型JMM指的是Javamemorymodel,它定义了主存,工作内存等抽象概念,相当于做一个隔离层,将底层CPU寄存器,缓存,硬件内存,CPU指令优化提供的功能通过一个简单接口给使用......
  • 开启win10测试模式
    1.鼠标右键2.管理员shell3.命令4.重启电脑......
  • 学习 Go,一段旅程:标准库包和并发 #5
    学习Go,一段旅程:标准库包和并发#5大家好!很高兴再次见到你,我希望你做得很好。在本文中,我想分享我在学习Go编程语言方面的进展。本周,我了解了标准库包和并发。标准库包......
  • NC210981 mixup2混乱的奶牛
    题目链接题目题目描述混乱的奶牛[DonPiele,2007]FarmerJohn的N(4<=N<=16)头奶牛中的每一头都有一个唯一的编号\(S_i(1<=S_i<=25,000)\).奶牛为她们的......
  • AGC010F 题解
    现在也就会写一写代码长度不超过\(1k\)的题目了。/kk看上去一脸不可做,看到从必败状态逆推的提示后会了。考虑什么算是必败状态,我们设此时棋子所在的位置为\(now\)......
  • day4:101-A3-计算机服务硬件
    101-A3-计算机服务硬件1.服务器2.主板3.CPU4.内存5.显卡6.硬盘7.raid卡服务器服务器,也称伺服器,是提供计算服务的设备。服务器在网络中为其它客户机(如PC机、智能......
  • day4:101-A2-Kali Linux系统调试
    kalilinux系统基础配置1.开启root用户登录root超级用户:拥有系统最高权限方法一sudo-s(注:终端输入命令sudo-s,输入密码)vi/etc/pam.d/gdm-autologin(注:进入页面,i进行......