admin 管理员组文章数量: 887021
并发
读者朋友,下午好!
今天分享一个很好地讲解并发中竞争条件的例子——银行在多个线程时候,随机在2个账户之间随机的转金额,在未加锁的时候,账户总金额会出乎意料的不一致;我们希望的是无论怎么转账,银行所有账户的总金额是固定不变的。
示例代码来源
《Java核心技术 卷1 第10版》 Core Java Volume I-Fundamentals(10th Edition)
[美] Cay S.Horstmann 著
周立新 陈波 叶乃文 邝劲筠 杜永萍 译
代码库:
git@github.com:cmhhcm/guiAndConcurrent.git
一、银行转账示例
Bank
package com.cmh.concurrent.unsynch;import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** Author: 起舞的日子* Date:2021/4/18 3:08 下午*/
public class Bank {private final double[] accounts;private Lock bankLock = new ReentrantLock();/*** 初始化银行** @param n the number of accounts* @param initialBalance the initial balance of each account*/public Bank(int n, double initialBalance) {accounts = new double[n];Arrays.fill(accounts, initialBalance);}/*** 从一个账户给另一个账户转账** @param from* @param to* @param amount*/public void transfer(int from, int to, double amount) {bankLock.lock();try {if (accounts[from] < amount) {return;}System.out.print(Thread.currentThread());accounts[from] -= amount;System.out.printf(" %10.2f from %d to %d", amount, from, to);accounts[to] += amount;System.out.printf(" Total Balance: %10.2f %n", getTotalBalance());System.out.println();} finally {bankLock.unlock();}}/*** 来看一下transfer这个方法字节码指令执行情况* javac Bank.java* javap -c -v Bank* <p>* 之后看到的是这样的:* 大体找到对应accounts[from] -= amount的指令:* 22: getfield #7 // Field accounts:[D 去from索引位置获取到这个值* 25: iload_1 将第二个int类型的值推送至栈顶* 26: dup2 复制栈顶的数值并将复制值压入栈顶* 27: daload 将double数组指定索引的值推送至栈顶* 28: dload_3 将第四个double型本地变量推送至栈顶* 29: dsub 将栈顶两double型数值相减并将结果压入栈顶* 30: dastore 将栈顶double型数值存入指定数组指定索引的位置* <p>* 通过以上指令,基本知道在accounts[from] = accounts[from] - amount的时候,* 至少需要压栈、详减、存入几个指令,那么在这个过程中,未做并发处理,就会有并发问题。*/public double getTotalBalance() {double sum = Arrays.stream(accounts).sum();return sum;}public int size() {return accounts.length;}
}
BankTest
package com.cmh.concurrent.unsynch;/*** This program shows data corruption when multiple threads access a data structure* <p>* Author: 起舞的日子* Date:2021/4/18 3:08 下午*/
public class UnsynchBankTest {public static final int NACCOUNTS = 100;public static final double INITIAL_BALANCE = 1000;public static final double MAX_ACCOUNT = 1000;public static final int DELAY = 10;public static void main(String[] args) {Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);for (int i = 0; i < NACCOUNTS; i++) {int fromAccount = i;Runnable runnable = () -> {try {while (true) {int toAccount = (int) (bank.size() * Math.random());double amount = MAX_ACCOUNT * Math.random();bank.transfer(fromAccount, toAccount, amount);Thread.sleep((int) (DELAY * Math.random()));}} catch (InterruptedException interruptedException) {interruptedException.printStackTrace();}};Thread thread = new Thread(runnable);thread.start();}}}
二、加锁核心代码
未加锁前运行效果:
加锁后运行效果:
三、原理分析
1、为什么会出现总金额不一致的情况?
因为accounts[from] = accounts[from] - amount的时候,
背后的JVM指令不是一个原子性操作,即是一行代码,背后是分几步来完成的。那么在这几步的过程中,就可能被别的线程“抢占”了(操作系统的分配规则)。
通过javap 可以查看编译后的Bank.class文件的这行代码的执行步骤:Bank类中已做详细注释说明。这里在重点讲一下查看流程:
第一步,编译Bank.java javac Bank.java 第二步:javap -c -v Bank
即可查看详细指令。-c -v详细含义见下图-
好了,看一下核心执行逻辑:
2、加锁怎么加?为什么用公平锁?
待后续补充
好了,再会!
本文标签: 并发
版权声明:本文标题:并发 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1687325961h89818.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论