Java面向对象程序设计|模拟银行存取款

文章图片
图5.5展示了不同线程对同一数据进行存取的两种情况 。 对情况a) , 因只存在读操作 , 类似多人看报 , 各线程获取的数据是确定的;对情况b) , 因有写操作 , 当线程以不同顺序执行时 , D读取到的值、x的最终值是不确定的 。 如以D-E-F次序执行 , D读到1 , x值为3;若以F-E-D次序执行 , D读到2 , x值为2 。

文章图片
■图5.5多个线程对同一数据读写的两种情况
这种多个线程对同一数据的并发读写(至少有一个线程执行写操作)被称作竞争 。 竞争会导致数据的不确定性 。 这种不确定性 , 有资料将其视为数据访问不安全 。
下面以银行存取钱为例介绍上述不确定性必须要被解决 。 假设银行有账户张三 , 账户余额100元 。 现张三及其儿子同时在不同存取款一体机上对该账户进行操作 。 张三存入200元 , 其儿子取出300元 。 假设使用存取一体机时 , 必须经历{查-改-查}三个步骤 。 为在本地显示余额和修改金额 , 一体机上需要有这两个本地变量:余额和输入的数据 , 见图5.6 。 为方便分析 , 图中对不同位置上的数据和指令做了不同标注 , 用c/q表示输入的存钱/取钱金额;cm/qm、m分别表示本地缓存余额和账户余额;c1、q1等表示不同机器上的动作 。
■图5.6两个线程向同一银行账户同时存取款
根据需求 , 有m=100 , c=200 , q=300 。 假设执行序列为{q1-c1-q2-c2-q3-c3} , 对应的指令序列:qm=m;cm=m;m=qm-q;m=cm+c;qm=m;cm=m;代入数据 , 结果如下:
qm=100;cm=100;m=100-300=-200;m=100+200=300;qm=300;cm=300
换言之 , 账户原有100元 , 存200 , 取300 , 执行完毕后 , 最终余额300 。 银行显然不能容忍 。 可能的执行序列很多 , 这里不再赘述 , 请读者自行分析 。
【Java面向对象程序设计|模拟银行存取款】上述现象是{c1-c2-c3}和{q1-q2-q3}以交错方式对同一账户竞争存取所致 。 若二者以“不可分割”的方式(也称独占方式或互斥方式)执行 , 如现存后取 , 或是先取后存 , 则不会出现上述状况 。 Java用synchronized(D){S}框架实现互斥 。 其中对象D称作临界资源 , 代码段S称作临界区 。 该框架表示:S只能以原子(即不可分割)方式访问D 。
01
线程的互斥机制-示例
【例5.5】假设账户张三有余额100元 , 对账户的存取钱过程的动作序列均为“查-改-查” 。 需要存入200 , 取出300 。 借助线程机制 , 模拟对张三的账户同时实施存取 。
目的:掌握互斥机制的实现和应用框架 。
设计:本例主要设计了两个类:账户类Accoun、实施存/取钱的ATM类 。 其中:
Account类有属性:姓名、金额 , 用构造函数对这些属性赋值 , 以及对金额的read/write方法、获取姓名的getName方法 。
ATM类涉及对账户存/取特定金额 , 故有两个属性:账户a、存/取金额atmVal(正数表示存钱、负数表示取钱) 。 为模拟同时执行 , ATM类必须是线程类 , 在run实施存钱或取钱 , 基本流程为“查-改-查” 。
注意:由于指令执行速度非常快 , 即使未用互斥框架 , 线程体运行也可能一次运行完毕(即存钱过程和取钱过程未发生指令交错) 。 这样难以发现设计问题 。 故线程体中加入了一些sleep动作 。 sleep执行时 , 当前线程必须放弃处理机控制权转入休眠 , 线程切换自然就发生了 。 因此加入sleep可模拟线程频繁切换情形(最坏情形) 。 这是调试线程的常用策略 。 另外 , sleep所属线程会放弃cpu , 但不会放弃对资源的占用(即不会打开锁) 。
- 程序员|程序员的口味变了!C++首次逆袭JAVA:跻身最受欢迎编程语言TOP3
- Java|华为笔记本双12大促开启 智慧体验让办公学习更高效
- Java|京东第一刀落下:多个副总裁卸任 刘强东嫡系人手接管
- Java|荣耀Magic5Pro很大胆,16GB+5000mAh+100W,香喷喷
- Java|MIUI14把精简当作核心卖点,国产系统急需做减法
- Java|“高薪专业户”的IT互联网行业是否已达到天花板?Java行业薪酬
- javascript|全民淘宝节|千万个淘宝卖家,有千万种答案
- 【框架】123:spring框架之面向切面编程
- 【微服务】146:商品品牌业务后台Java代码编写
- 【微服务】162:利用Java实现索引库相关的分页、排序和聚合
