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

Posted lytwajue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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


上面的输出结果基本一致,最后一个结果出现小数问题,主要是因为我数据精度的问题,兴许能够通过其它设置来避免。


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


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


技术分享





























以上是关于java基础入门-多线程同步浅析-以银行转账为样例的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程基础(入门篇)

Python的线程04 同步机制实际应用场景举例

Python的线程04 同步机制实际应用场景举例

Java进阶 线程安全

Java多线程

Java线程安全