首页 > 编程语言 >Java多线程转账

Java多线程转账

时间:2023-11-28 17:24:57浏览次数:36  
标签:转账 Account balance Java lock 线程 多线程 public

Java多线程转账

关键词:多线程,Java

以前的一道面试题,要求是使用Java多线程,实现一个转账业务。不考虑数据库,不考虑其他第三方系统。只考虑当前Java程序内各个账户进行转账,保证转账金额正确性和转账功能效率。

想起那大约还是两年前,是线上面试,面试官给完题目就关闭视频通话,让我自己去写代码,并且告知可以看浏览器。

要是放到现在可不行了哈!直接ChatGPT,分分钟就写好了,而且各种说辞都能准备好!


实现代码

代码地址

这里使用充血模型:Account

package transfer;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author keboom
 * @date 2021/8/24
 */
public class Account {

    private Long id;
    private double balance;
    private ReentrantLock lock = new ReentrantLock();
    // 记录转账次数,没什么用,只是为了验证transfer执行的次数
    AtomicInteger count = new AtomicInteger(0);

    public Account(Long id, double balance) {
        this.id = id;
        this.balance = balance;
    }

    public void deposit(double amount) {
        lock.lock();
        balance += amount;
        lock.unlock();
    }

    public void withdraw(double amount) {
        lock.lock();
        balance -= amount;
        lock.unlock();
    }

    public double getBalance() {
        return balance;
    }

    public boolean transfer(Account target, double amount) {
        if (this == target) {
            System.out.println("不能自己转给自己");
            return false; // 防止自己向自己转账
        }

        if (this.getBalance() < amount) {
            System.out.println("余额不足,转账失败");
            return false; // 余额不足,转账失败
        }
        // avoid deadlock
        // 使用两把锁,按照账户的 hashcode 顺序来避免死锁
        Account firstLock = this.hashCode() > target.hashCode() ? this : target;
        Account secondLock = this.hashCode() > target.hashCode() ? target : this;
        boolean flag = true;
        while (flag) {
            if (firstLock.lock.tryLock()) {
                try {
                    if (secondLock.lock.tryLock()) {
                        try {
                            this.withdraw(amount);
                            target.deposit(amount);
                            count.incrementAndGet();
                            flag = false;
                            Thread.sleep(1);
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        } finally {
                            secondLock.lock.unlock();
                        }
                    }
                } finally {
                    firstLock.lock.unlock();
                }
            }
        }
        return true;
    }

}

首先是余额的正确性,balance 并不需要使用 volatile 关键字修饰,因为 balance 变量的访问是通过锁来保护的。在使用 ReentrantLock 进行加锁和解锁的过程中,会保证对 balance 变量的读取和写入操作在同一时刻只能被一个线程访问,从而确保了线程间的可见性和原子性。

转账方法,会有多线程进行调用,因此需要锁来控制。为了防止死锁问题,我们通过hash值来得到一个加锁顺序。

接下来使用线程池并发调用转账方法:

package transfer;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 这个平台来进行转账操作
 *
 * @author keboom
 * @date 2023/11/28
 */
public class TransferTask {

    public static void main(String[] args) throws InterruptedException {
        Account accountA = new Account(1L, 100000);
        Account accountB = new Account(1L, 100000);

        ExecutorService toA = Executors.newFixedThreadPool(3);
        ExecutorService toB = Executors.newFixedThreadPool(3);


        for (int i = 0; i < 1000; i++) {
            toB.execute(() -> {
                accountB.transfer(accountA, 10);
            });
        }

        for (int i = 0; i < 1000; i++) {
            toA.execute(() -> {
                accountA.transfer(accountB, 10);
            });
        }

        toA.shutdown();
        toB.shutdown();

        toA.awaitTermination(1, TimeUnit.MINUTES);
        toB.awaitTermination(1, TimeUnit.MINUTES);
        System.out.println("accountA: " + accountA.getBalance()+ "  " + accountA.count);
        System.out.println("accountB: " + accountB.getBalance()+ "  " + accountB.count);

    }
}

这里我们总共用6个线程去模拟。因为同一时刻只有一个线程能执行转账操作。如果线程过多,会发生大量的线程争抢,会有一些线程饿死。

比如我实验了使用20个线程并发执行转账。发现一个是执行时间变得很长,而且到最后返回时结果也不正确,发现执行次数少了。猜测应该是一些线程饿死,导致一些任务并没有执行。

标签:转账,Account,balance,Java,lock,线程,多线程,public
From: https://www.cnblogs.com/keboom/p/17862445.html

相关文章

  • 《Effective Java》阅读笔记-第二章
    EffectiveJava阅读笔记第二章创建和销毁对象第1条:用静态工厂方法代替构造器静态工厂方法优势:静态工厂方法有名称静态工厂方法可以详细的指定名称,而使用构造器时如果没有文档会难以区分不同构造器之间的区别。不必每次调用的时候创建一个新对象静态工厂方法可以缓......
  • java字符串String类的常用方法
    java字符串String类的常用方法字符串的创建:(1)定义字符串直接赋值,在字符串池中开辟空间()Stringstr1=“Hello”;//在字符串池中写入字符串"hello"Stringstr2=“Hello”;//直接引用字符串池中的"Hello"System.out.println(str1==str2);//地址相同,输出:true(2)使用new关键字调用字......
  • 秦疆的Java课程笔记:42 流程控制 增强For循环
    Java5引入的一种主要用于数组或集合的增强型for循环。这里只是先了解一下。格式如下:for(声明语句:表达式){ //代码语句}声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用于限定在循环语句块,其值与此数组元素的值相等。表达式:表达式是要访问的数组......
  • 秦疆的Java课程笔记:43 流程控制 break、continue、goto
    break:在任何循环语句的主体部分,均可用break控制循环的流程。break用于强行退出循环,不执行循环中剩余的语句。(break也在switch语句中使用)publicclassBreakDemo{publicstaticvoidmain(String[]args){inti=0;while(i<100){......
  • 秦疆的Java课程笔记:44 流程控制 打印三角形及Debug
    作业:打印5行三角形这是我写的:publicclassTestDemo1{publicstaticvoidmain(String[]args){intline=5;//定义总行数linefor(inti=1;i<=line;i++){//i是循环输出每一行for(intj=1;j<=line-i;j++){//j......
  • Java核心知识体系7:线程安全性讨论
    Java核心知识体系1:泛型机制详解Java核心知识体系2:注解机制详解Java核心知识体系3:异常机制详解Java核心知识体系4:AOP原理和切面应用Java核心知识体系5:反射机制详解Java核心知识体系6:集合框架详解1为什么需要多线程我们都知道,CPU、内存、I/O设备的速度是有极大差异的,为了合......
  • Java面试小练(四)
    请描述GET请求方式与POST请求方式的区别?post比get更安全,发送数据更大get和post都是http和服务器交互的方式get会将请求的数据放在url中,http协议头,中间用?来链接,用&来相连数据,中文会进行url加密post会将数据放在http的包体内发送get请求数据放在url,理论上没有大小限制,但是浏......
  • Java语言基础知识全总结
    一.Java的优点1.      跨平台性。一次编译,到处运行。Java编译器会将Java代码编译成能在JVM上直接运行的字节码文件,C++会将源代码编译成可执行的二进制代码文件,所以C++执行速度快2.      纯面向对象。Java所有的代码都必须在类中书写。C++兼具面向对象和面向过程的特......
  • 多线程网络通信
    当多客户端同时连接和服务端持续监听时,涉及到多线程,每当有新的客户端连接时,就创建一个新的线程来处理与该客户端的通信,从而允许服务器端同时与多个客户端建立连接。直接贴代码:1//main.cpp2#include<QCoreApplication>3#include<iostream>4#include<QDebug>5#......
  • Java开发者的Python快速进修指南:实战之简易跳表
    前言之前我已经将Python的基本语法与Java进行了比较,相信大家对Python也有了一定的了解。我不会选择去写一些无用的业务逻辑来加强对Python的理解。相反,我更喜欢通过编写一些数据结构和算法来加深自己对Python编程的理解。学习任何语言都一样。通过编写数据结构和算法,不仅可以加......