• java基础入门-多线程同步浅析-以银行转账为例子


    在说之前先普及一下线程是什么?

    线程:说白了就是一个任务片段

    进程:是一个具有独立功能的程序关于某个数据集合的一次运行活动,一个进程有一个或者多个线程


    线程与进程的本质区别就是有么有数据共享空间,线程之间可以共享数据,进程不可以


    下面进入主题:线程间的同步

    由于现在业务流程增加,业务节点也增加,使用业务的人员也同时增加,这个时候就不可避免的出现并发问题,多个线程同时访问操作某一个数据单元

    我们以银行转账为例说明,下面先上代码:

    建立一个银行的类,里面主要包括三个方法,一个是转账,一个是得到现有银行存款总数,一个是得到现在存户数量

    public class Bank {
    
    	private final double[] accounts;
    
    	public Bank(int n, double initialBalance) {
    		accounts = new double[n];
    		for (int i = 0; i < accounts.length; i++) {
    			accounts[i] = initialBalance;
    		}
    	}
    
    	public void transfer(int from, int to, double amount) {
    		if (accounts[from] < amount) {
    			return;
    		}
    		System.out.println(Thread.currentThread());
    		accounts[from] -= amount;
    		System.out.printf("%f from %d to %d ", amount, from, to);
    		accounts[to] += amount;
    		System.out.println("total:" + getTotalBalance());
    	}
    
    	public double getTotalBalance() {
    		double sum = 0d;
    		for (int i = 0; i < accounts.length; i++) {
    			sum += accounts[i];
    		}
    		return sum;
    	}
    
    	public int getAccountSize() {
    		return accounts.length;
    	}
    }
    


    下面是转账类,因为需要并发操作,所以实现Runnable接口


    public class TransferRunnable implements Runnable {
    
    
    	private Bank bank;
    	private int fromAccount = 0;
    	private double maxAmount = 0;
    
    
    	public TransferRunnable(Bank b, int fromAccount, double maxAmount) {
    		this.bank = b;
    		this.fromAccount = fromAccount;
    		this.maxAmount = maxAmount;
    	}
    
    
    	@Override
    	public void run() {
    		double amount = maxAmount * Math.random();
    		int toAccount = (int) ((int) bank.getAccountSize() * Math.random());
    		bank.transfer(fromAccount, toAccount, amount);
    		try {
    			Thread.sleep((long) (100L * Math.random()));
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    
    
    }
    

    下面是测试类:

    public class Test {
    	public static void main(String[] args) {
    		Bank bank = new Bank(100, 1000);
    		for (int i = 0; i < 3; i++) {
    			TransferRunnable transferRunnable = new TransferRunnable(bank, i,
    					1000);
    			Thread thread = new Thread(transferRunnable);
    			thread.start();
    		}
    	}
    }
    

    输出结果:

    Thread[Thread-0,5,main]
    Thread[Thread-2,5,main]
    Thread[Thread-1,5,main]


    430.796266 from 0 to 75

    714.274395 from 1 to 88

    849.880218 from 2 to 33 


    total:98435.8453871716
    total:99150.11978192833
    total:100000.0

    我们看上面的结果,特别是最后三行的total总数,发现,第一第二次转账后,总数不对了,仔细观察打印结果,由于并行执行任务,而且中间由于是由cup分配执行顺序,所以我们看到的结果并没有完全按照我们的方法所实现的那样输出出来

    由于出现这样的问题,我们引入“锁”的概念,由于这里面是浅析,就不针对锁详细说明,下面我们在bank类里面的转账方法上面加上最简单最常用的锁synchronized,看看结果是怎样:

    public class Bank {
    
    	private final double[] accounts;
    
    	public Bank(int n, double initialBalance) {
    		accounts = new double[n];
    		for (int i = 0; i < accounts.length; i++) {
    			accounts[i] = initialBalance;
    		}
    	}
    
    	//加了锁
    	public synchronized void transfer(int from, int to, double amount) {
    		if (accounts[from] < amount) {
    			return;
    		}
    		System.out.println(Thread.currentThread());
    		accounts[from] -= amount;
    		System.out.printf("%f from %d to %d ", amount, from, to);
    		accounts[to] += amount;
    		System.out.println("total:" + getTotalBalance());
    	}
    
    	public double getTotalBalance() {
    		double sum = 0d;
    		for (int i = 0; i < accounts.length; i++) {
    			sum += accounts[i];
    		}
    		return sum;
    	}
    
    	public int getAccountSize() {
    		return accounts.length;
    	}
    }
    


    输出结果:

    Thread[Thread-0,5,main]
    187.754955 from 0 to 50 total:100000.0


    Thread[Thread-1,5,main]
    282.138799 from 1 to 90 total:100000.0


    Thread[Thread-2,5,main]
    217.089515 from 2 to 86 total:100000.00000000001


    上面的输出结果基本一致,最后一个结果出现小数问题,主要是由于我数据精度的问题,后续可以通过其他设置来避免。


    程序里面由于出现了锁,所以在性能上面不可避免的出现下降,特别是在一些大型程序里面,所以这里面需要根据实际业务所需,把锁的范围锁到比较小的范围,使得性能不会大幅度的下降。


    最后我们把上面的东西总结出一个图





    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    一些必不可少的Sublime Text 2插件
    sublime text 使用小技巧
    Azure Queue 和 Service Bus Queue的比较
    怎么使用Windows Azure Queue Storage 服务
    配置 SharePoint 2010 使用本机默认 SQL Server 实例 Pan
    将两个字符串中相同的地方str2无重复的输出
    javascript笔记
    C#中怎样使控件随着窗体一起变化大小(拷来学习)
    在pictureBox中画方格矩阵地图,方法比较笨,有好方法望指导
    通过一个小推理写出的程序,结果出乎意料……有哪位知道为什么吗 已解决
  • 原文地址:https://www.cnblogs.com/raylee2007/p/4774567.html
Copyright © 2020-2023  润新知