这个示例模拟的是2个人相互转账的过程:
个人账户实体类:
public class UserAccount {
private final String name;//账户名称
private int money;//账户余额
private final Lock lock = new ReentrantLock();
public Lock getLock() {
return lock;
}
public UserAccount(String name, int amount) {
this.name = name;
this.money = amount;
}
public String getName() {
return name;
}
public int getAmount() {
return money;
}
@Override
public String toString() {
return "UserAccount{" +
"name='" + name + '\'' +
", money=" + money +
'}';
}
//转入资金
public void addMoney(int amount){
money = money + amount;
}
//转出资金
public void flyMoney(int amount){
money = money - amount;
}
}
转账动作接口:
public interface ITransfer {
void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException;
}
不安全的转账动作实现:
先锁转出方,再锁转入方看似没有问题。可如果当A转给B钱且获得A锁时,B也恰好转给A钱且获得B锁,不就形成死锁了吗?任何一方永远也不能同时得到A锁和B锁,程序无法运行下去,且很难一眼看出死锁问题。
public class TransferAccount implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
synchronized (from){
System.out.println(Thread.currentThread().getName()
+" get"+from.getName());
Thread.sleep(100);
synchronized (to){
System.out.println(Thread.currentThread().getName()
+" get"+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
模拟转账动作:
public class PayCompany {
/*执行转账动作的线程*/
private static class TransferThread extends Thread{
private String name;
private UserAccount from;
private UserAccount to;
private int amount;
private ITransfer transfer;
public TransferThread(String name, UserAccount from, UserAccount to,
int amount, ITransfer transfer) {
this.name = name;
this.from = from;
this.to = to;
this.amount = amount;
this.transfer = transfer;
}
public void run(){
Thread.currentThread().setName(name);
try {
transfer.transfer(from,to,amount);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
UserAccount Jack = new UserAccount("Jack",20000);
UserAccount Rose = new UserAccount("Rose",20000);
ITransfer transfer = new TransferAccount();
// ITransfer transfer = new SafeOperate();
// ITransfer transfer = new SafeOperateToo();
TransferThread JackToRose = new TransferThread("JackToRose"
,Jack,Rose,2000,transfer);
TransferThread RoseToJack = new TransferThread("RoseToJack"
,Rose,Jack,4000,transfer);
JackToRose.start();
RoseToJack.start();
}
}
结果:
不会产生死锁的安全转账方式一:
强制定义顺序,如:
比较2个对象的哈希值,谁小先锁谁。如果一样,先抢第三把锁,拿到第三把锁的才能继续拿第一第二把锁。
public class SafeOperate implements ITransfer {
private static Object tieLock = new Object();
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if(fromHash<toHash){
synchronized (from){
System.out.println(Thread.currentThread().getName()+" get "+from.getName());
Thread.sleep(100);
synchronized (to){
System.out.println(Thread.currentThread().getName()+" get "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
}
}
}else if(toHash<fromHash){
synchronized (to){
System.out.println(Thread.currentThread().getName()+" get"+to.getName());
Thread.sleep(100);
synchronized (from){
System.out.println(Thread.currentThread().getName()+" get"+from.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
}
}
}else{
synchronized (tieLock){
synchronized (from){
synchronized (to){
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
}
}
结果:
不会产生死锁的安全转账方式二:
每一个UserAccount里加一个显式锁,进行循环尝试拿锁,只用同时拿到2个锁,才能执行操作。
public class SafeOperateToo implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
Random r = new Random();
while(true){
if(from.getLock().tryLock()){
System.out.println(Thread.currentThread().getName()
+" get"+from.getName());
try{
if(to.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName()
+" get"+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
break;
}finally{
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
//Thread.sleep(r.nextInt(2));
}
}
}
结果:
可以看到,虽然最后完成了互相转账动作,但是尝试拿到2把锁的时间过长。这是因为这2个转账动作总是先尝试拿到自己的锁,然后都拿不到另一把锁,又都释放锁再次尝试。直到因为系统调度恰好有一边同时成功拿到2把锁。这种现象叫做活锁。
解决办法:每个线程休眠随机数,错开拿锁的时间。
结果: