在说之前先普及一下线程是什么?
线程:说白了就是一个任务片段
进程:是一个具有独立功能的程序关于某个数据集合的一次运行活动,一个进程有一个或者多个线程
线程与进程的本质区别就是有么有数据共享空间,线程之间可以共享数据,进程不可以
下面进入主题:线程间的同步
由于现在业务流程增加,业务节点也增加,使用业务的人员也同时增加,这个时候就不可避免的出现并发问题,多个线程同时访问操作某一个数据单元
我们以银行转账为例说明,下面先上代码:
建立一个银行的类,里面主要包括三个方法,一个是转账,一个是得到现有银行存款总数,一个是得到现在存户数量
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
上面的输出结果基本一致,最后一个结果出现小数问题,主要是由于我数据精度的问题,后续可以通过其他设置来避免。
程序里面由于出现了锁,所以在性能上面不可避免的出现下降,特别是在一些大型程序里面,所以这里面需要根据实际业务所需,把锁的范围锁到比较小的范围,使得性能不会大幅度的下降。
最后我们把上面的东西总结出一个图