多线程-线程间的同步
Java多线程ATM转账
在没有同步时数据错误
账户有转账等功能, 文件Account.java
public class Account {
protected String id;
protected long balance;
public Account(String id, long balance) {
this.id = id;
this.balance = balance;
}
public long getBalance() {
return this.balance;
}
public void deposit(long amount) {
this.balance += amount;
}
public void withdraw(long amount) {
this.balance -= amount;
}
public void transfer( Account to, long amount) {
this.withdraw(amount);
to.deposit(amount);
}
}
AtmThread模仿大量并发转账, 文件AtmThread.java
public class AtmThread extends Thread {
Account accountFrom;
Account accountTo;
public AtmThread(Account from, Account to) {
this.accountFrom = from;
this.accountTo = to;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
this.accountFrom.transfer(accountTo, 1);
}
System.out.println("mission complete.");
}
}
让2个线程在2个账户之间进行多次并发转账的主函数. 文件ATMMain.java
public class ATMMain {
public static void main(String[] args) {
Account account1 = new Account("A001", 10000);
Account account2 = new Account("A002", 10000);
AtmThread atmThread1 = new AtmThread(account1, account2);
AtmThread atmThread2 = new AtmThread(account2, account1);
atmThread1.start();
atmThread2.start();
try {//主线程等待1000ms
Thread.sleep(1000);
} catch (InterruptedException ex) {
System.out.println(ex);
}
System.out.println(account1.getBalance());
System.out.println(account2.getBalance());
}
}
运行结果
每次的运行结果并不相同, 特别是两个账户的余额的总和也不一定等于20000.
第1次运行结果:
mission complete.
mission complete.
9827
9779
第2次运行结果:
mission complete.
mission complete.
10045
9887
第3次运行结果:
mission complete.
mission complete.
9835
10112
恰当的同步后数据正常
方案1. 对转账的2个账户进行同步
根据账号的大小, 依次锁定转账涉及的2个账户.
如果对2各个账户同步(锁定)没有顺序, 可能发生死锁.
如果只对1个账户的锁定, 那么达不到锁定效果.
具体方案: 修改Account, 增加同步转账功能的文件Account.java
public class Account {
protected String id;
protected long balance;
public Account(String id, long balance) {
this.id = id;
this.balance = balance;
}
public long getBalance() {
return this.balance;
}
public void deposit(long amount) {
this.balance += amount;
}
public void withdraw(long amount) {
this.balance -= amount;
}
/**
*
* @param s1
* @param s2
* @return 1 : s1>s2, -1: s1<s2, 0: a1==a2
*/
public int compare(String s1, String s2) {
int minLength = s1.length() > s2.length() ? s2.length() : s1.length();
for (int i = 0; i < minLength; i++) {
if (s1.charAt(i) > s2.charAt(i)) {
return 1;
} else if (s1.charAt(i) < s2.charAt(i)) {
return -1;
}
}
if (s1.length() > s2.length()) {
return 1;
} else if (s2.length() > s1.length()) {
return -1;
} else {
return 0;
}
}
public void transfer(Account to, long amount) {
Account a1;
Account a2;
switch (compare(this.id, to.id)) {
case 1:
a1 = this;
a2 = to;
break;
case -1:
a1 = to;
a2 = this;
break;
case 0:
default:
return;
}
synchronized (a1) {
synchronized (a2) {
this.withdraw(amount);
to.deposit(amount);
}
}
}
}
运行结果:
mission complete.
mission complete.
10000
10000
方案2. 对转账的方法进行同步
在AtmThread中增加一个同步的(synchronized)的静态的(static)转账功能.
如果方法不是static的, 那么不同的线程对象有不同转账功能,达不到同步所有转账操作的作用.
这个解决方案看起来比上面的优雅. 但是转账功能1次只能被1个线程使用, 在有大量并发转账时, 难以在CPU的多个核心上用多线程转账提高效率.
具体方案:修改文件AtmThread.java, 增加方法static synchronized void transfer2(),并使用transfer2()进行转账.
public class AtmThread extends Thread {
Account accountFrom;
Account accountTo;
public AtmThread(Account from, Account to) {
this.accountFrom = from;
this.accountTo = to;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
AtmThread.transfer2(accountFrom , accountTo, 1);
}
System.out.println("mission complete.");
}
public static synchronized void transfer2(Account from, Account to, long amount) {
from.withdraw(amount);
to.deposit(amount);
}
}
运行结果:
mission complete.
mission complete.
10000
10000
方案3. 为每个对象的存和取功能增加同步
depisit()和withdraw()只涉及1个账户的操作. 不需要同步(锁定)from和to两个账户.
因此不存在死锁问题. 使用synchronized修饰deposit()函数, 是锁定当前对象(this), 执行deposit(). 为depisit()和withdraw()增加synchronized, 则对一个对象的存取一次只能执行一个方法, 不会产生多个线程同时修改balance, 导致balance的值发生错误的问题.
此种解决方案是推荐的解决方案.
具体方案: 为depisit()和withdraw()增加synchronized, 修改Account.java
public class Account {
protected String id;
protected long balance;
public Account(String id, long balance) {
this.id = id;
this.balance = balance;
}
public long getBalance() {
return this.balance;
}
public synchronized void deposit(long amount) {
this.balance += amount;
}
public synchronized void withdraw(long amount) {
this.balance -= amount;
}
public void transfer(Account to, long amount) {
this.withdraw(amount);
to.deposit(amount);
}
}
运行结果:
mission complete.
mission complete.
10000
10000
transfer中包括对deposit和withdraw的调用, 如果在transfer前加上synchronized修饰, 会导致对this对象和to对象的依次锁定. 如果有另一个线程同时对这两个对象相反的调用transfer, 可能会导致两个对象依次被两个线程锁定, 互相等待要锁定对方线程已经锁定的资源, 导致死锁.