赞
踩
是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
产生死锁的必要条件:
互斥条件:所谓互斥就是进程在某一时间内独占资源。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
我们模拟最常见的转账业务:A账户给B账户转账的同时, B账户又在给A账户转账。此时会发生死锁。
- /**
- * 用户账户类
- */
- public class UserAccount {
- //账户id(唯一的值)可能是银行卡号
- private final String id;
- //账户余额
- private Double money;
-
- public UserAccount(String id, Double amount) {
- this.id = id;
- this.money = amount;
- }
- public String getId() {
- return id;
- }
-
-
- public Double getMoney() {
- return money;
- }
-
-
- @Override
- public String toString() {
- return "UserAccount [id=" + id + ", money=" + money + "]";
- }
- /**
- * 转入资金
- * @param amout 金额
- */
- public void addMoney(double amout) {
- money = money + amout;
- System.out.println("账户:" + id + " ,转入"+ amout + "元,当前账户余额:" + money);
- }
-
- /** 转出资金
- * @param amout 金额
- */
- public void flyMoney(double amout) {
- if ((money - amout) >0) {
- money = money - amout;
- System.out.println("账户:" + id + " ,转出"+ amout + "元,当前账户余额:" + money);
- }else {
- System.out.println("账户余额不足!当前余额:" + money);
- }
- }
- }
- /**
- * 转账动作接口
- */
- public interface ITransfer {
- /**
- *
- * @param from 转出账户
- * @param to 转入账户
- * @param amount 转账金额
- * @throws InterruptedException
- */
- void transfer(UserAccount from, UserAccount to, double amount) throws InterruptedException;
- }
- // 执行交易的线程任务
- private static class TransferThread extends Thread{
- // 线程名称
- private String name;
- // 转出账户
- private UserAccount from;
- // 转入账户
- private UserAccount to;
- // 交易金额
- private Double amount;
- // 交易方式
- private ITransfer transfer;
-
- public TransferThread(String name, UserAccount from, UserAccount to, Double amount, ITransfer transfer) {
- super();
- this.name = name;
- this.from = from;
- this.to = to;
- this.amount = amount;
- this.transfer = transfer;
- }
-
- @Override
- public void run() {
- Thread.currentThread().setName(name);
- try {
- transfer.transfer(from,to,amount);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 不安全的交易方式实现
- * @author James Lee
- *
- */
- public class UnSafeTransfer implements ITransfer{
-
- @Override
- public void transfer(UserAccount from, UserAccount to, double amount) throws InterruptedException {
- // 锁定转出账户
- synchronized (from) {
- System.out.println(Thread.currentThread().getName()+" 拿到【 " + from.getId() + "】账户的执行权!" );
- SleepTools.ms(1000);
- // 再锁定转入账户
- synchronized (to) {
- System.out.println(Thread.currentThread().getName()+" 拿到【 " + to.getId() + "】账户的执行权!" );
- // 这里代表两个账户的锁都拿到了才可以实现交易
- from.flyMoney(amount);
- to.addMoney(amount);
- }
- }
-
- }
-
- }
- public static void main(String[] args) {
-
- UserAccount james = new UserAccount("622...181: james", 100.00);
- UserAccount kobe = new UserAccount("622...182: kobe", 100.00);
- ITransfer transfer = new UnSafeTransfer();
- // 模拟 james给kobe转账20块钱
- TransferThread transferThread = new TransferThread("交易线程一", james, kobe, 20.00, transfer);
- // 模拟 kobe给james转账50块钱
- TransferThread transferThread2 = new TransferThread("交易线程二", kobe, james, 50.00, transfer);
- transferThread.start();
- transferThread2.start();
- }
两个交易线程一直在等对方释放锁。
解决死锁的思路:就是保证线程的顺序性。
有了思路以后,我们可以再在程序里控制锁账户的顺序性。
根据实体的hashCode来判定,hashCode低的先锁定,高的后锁定,使用synchronized 加锁
代码实现:
- import xiangxue.day09.bank.UserAccount;
- import xiangxue.tools.SleepTools;
-
- /**
- * 不会产生死锁的安全转账: 基于hashCode或者实体的唯一主键
- *
- */
- public class SafeTransferByHashCode implements ITransfer {
-
- private static Object tieLock = new Object();//加时赛锁
-
- @Override
- public void transfer(UserAccount from, UserAccount to, double amount) throws InterruptedException {
- // 首先获取实体账户的hashcode,考虑有可能传入之前会重写hashcode,所以我们调用jdk的identityHashCode获取原生值
- int fromHash = System.identityHashCode(from);
- int toHash = System.identityHashCode(to);
-
- // 先锁hash值小的账户
- if (fromHash < toHash) {
- synchronized (from) {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + from.getId() + "】账户的执行权!");
- SleepTools.ms(1000);
- // 再锁定转入账户
- synchronized (to) {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + to.getId() + "】账户的执行权!");
- // 这里代表两个账户的锁都拿到了才可以实现交易
- from.flyMoney(amount);
- to.addMoney(amount);
- }
- }
- }
- else if (toHash < fromHash) {
- synchronized (to) {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + to.getId() + "】账户的执行权!");
- SleepTools.ms(1000);
- // 再锁定转入账户
- synchronized (from) {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + from.getId() + "】账户的执行权!");
- // 这里代表两个账户的锁都拿到了才可以实现交易
- from.flyMoney(amount);
- to.addMoney(amount);
- }
- }
- }
- // 考虑到有hash冲突,这里在构造一把锁,让线程抢占,谁先抢占到,就执行。类似于篮球比赛,打成平手后的加时赛
- // hash冲突的概率是千万分之一,锁三次,对整体的性能其实影响不大
- else {
- synchronized (tieLock) {
- // 这里先锁哪个账户的顺序已经不重要了
- synchronized (from) {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + from.getId() + "】账户的执行权!");
- synchronized (to) {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + to.getId() + "】账户的执行权!");
- // 这里代表两个账户的锁都拿到了才可以实现交易
- from.flyMoney(amount);
- to.addMoney(amount);
- }
- }
- }
- }
-
- }
-
- }
- public static void main(String[] args) {
- PayCompany payCompany = new PayCompany();
- UserAccount james = new UserAccount("622...181: james", 100.00);
- UserAccount kobe = new UserAccount("622...182: kobe", 100.00);
- ITransfer transfer = new SafeTransferByHashCode();
- // 模拟 james给kobe转账20块钱
- TransferThread transferThread = new TransferThread("交易线程一", james, kobe, 20.00, transfer);
- // 模拟 kobe给james转账50块钱
- TransferThread transferThread2 = new TransferThread("交易线程二", kobe, james, 50.00, transfer);
- transferThread.start();
- transferThread2.start();
- }
简洁明了,容易理解,不足是代码太多了,可能初中级java开发会这样。sync是独占锁,了解JUC编程的可能还知道一种可重入锁,Lock。
使用lock 尝试性的拿锁
我们在用户账户类UserAccount.java 上加上以下代码:
- //显示锁
- private final Lock lock = new ReentrantLock();
-
- public Lock getLock() {
- return lock;
- }
- import xiangxue.day09.bank.UserAccount;
-
- /**
- * 不会产生死锁的安全转账: 使用可重入锁
- */
- public class SafeTransferByLock implements ITransfer{
-
- @Override
- public void transfer(UserAccount from, UserAccount to, double amount) throws InterruptedException {
- while (true) {
- // 尝试拿到转出账户的锁
- if (from.getLock().tryLock()) {
- try {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + from.getId() + "】账户的执行权!");
- // 再尝试拿转入账户的锁
- if (to.getLock().tryLock()) {
- try {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + to.getId() + "】账户的执行权!");
- // 这里代表两个账户的锁都拿到了才可以实现交易
- from.flyMoney(amount);
- to.addMoney(amount);
- break;
- } finally {
- // 释放转入账户的锁
- to.getLock().unlock();
- }
-
- }
- } finally {
- // 释放转出账户的锁
- from.getLock().unlock();
- }
- }
- }
- }
- }
- public static void main(String[] args) {
- PayCompany payCompany = new PayCompany();
- UserAccount james = new UserAccount("622...181: james", 100.00);
- UserAccount kobe = new UserAccount("622...182: kobe", 100.00);
- ITransfer transfer = new SafeTransferByLock();
- // 模拟 james给kobe转账20块钱
- TransferThread transferThread = new TransferThread("交易线程一", james, kobe, 20.00, transfer);
- // 模拟 kobe给james转账50块钱
- TransferThread transferThread2 = new TransferThread("交易线程二", kobe, james, 50.00, transfer);
- transferThread.start();
- transferThread2.start();
- }
在获取线程锁加上时间间隔,让程序休眠
改进SafeTransferByLock.java
- import java.util.Random;
-
- import xiangxue.day09.bank.UserAccount;
- import xiangxue.tools.SleepTools;
-
- /**
- * 不会产生死锁的安全转账: 使用可重入锁
- */
- public class SafeTransferByLock implements ITransfer{
-
- @Override
- public void transfer(UserAccount from, UserAccount to, double amount) throws InterruptedException {
- Random r = new Random();
- while (true) {
- // 尝试拿到转出账户的锁
- if (from.getLock().tryLock()) {
- try {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + from.getId() + "】账户的执行权!");
- // 再尝试拿转入账户的锁
- if (to.getLock().tryLock()) {
- try {
- System.out.println(Thread.currentThread().getName() + " 拿到【 " + to.getId() + "】账户的执行权!");
- // 这里代表两个账户的锁都拿到了才可以实现交易
- from.flyMoney(amount);
- to.addMoney(amount);
- break;
- } finally {
- // 释放转入账户的锁
- to.getLock().unlock();
- }
-
- }
- } finally {
- // 释放转出账户的锁
- from.getLock().unlock();
- }
- }
- // 休眠。解决活锁
- SleepTools.ms(r.nextInt(10));
- }
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。