死锁,一组互相竞争的资源的线程之间相互等待,导致永久阻塞的现象。
如下图所示:
 
 与死锁对应的,还有活锁,是指线程没有出现阻塞,但是无限循环。
有一个经典的银行转账例子如下:
我们有个账户类,其中有两个属性:账户名和余额。
 它有两个方法:转入、转出。
public class Account {
    private String countName;
    private int balance;
    public Account(String countName, int balance) {
        this.countName = countName;
        this.balance = balance;
    }
    /**
     * 转出金额,更新转出方余额,金额减少
     * @param amount
     */
    public void debit(int amount){
        this.balance -= amount;
    }
    /**
     * 存入金额,更新转入方余额,金额增多
     * @param amount
     */
    public void credit(int amount){
        this.balance += amount;
    }
    public String getCountName() {
        return countName;
    }
    public void setCountName(String countName) {
        this.countName = countName;
    }
    public int getBalance() {
        return balance;
    }
    public void setBalance(int balance) {
        this.balance = balance;
    }
}
还有一个转账操作类,它有三个属性:转入账户、转出账户、转账金额。
 它实现了Runnable接口,run方法中是转账逻辑代码。
 我们使用 while(true) 让他不停的进行转账操作:如果账户余额大于转账金额,就让转出账号减少amount,转入账户增加amount。打印出线程名、金额转移方向、以及每个账户余额。
 我们在main方法中进行测试,创建两个转账账户,使用两个线程操作这两个账户,让他们相互转账。
public class TransferAccount implements Runnable{
    private Account fromAccount;
    private Account toAccount;
    private int amount;
    public TransferAccount(Account fromAccount, Account toAccount, int amount) {
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.amount = amount;
    }
    @Override
    public void run() {
        while(true){
            synchronized (fromAccount){
                synchronized (toAccount){
                    if(fromAccount.getBalance()>=amount) {
                        fromAccount.debit(amount);
                        toAccount.credit(amount);
                    }
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(fromAccount.getCountName()+"->"+toAccount.getCountName()+":"+amount );
                    System.out.println(fromAccount.getCountName() +"账户余额:"+ fromAccount.getBalance());
                    System.out.println(toAccount.getCountName() +"账户余额:"+ toAccount.getBalance());
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Account bigHead = new Account("冤大头",100000);
        Account smallKill = new Account("门小抠",200000);
        new Thread(new TransferAccount(bigHead,smallKill,10)).start();
        new Thread(new TransferAccount(smallKill,bigHead,20)).start();
    }
}
执行main方法后的输出结果:
Thread-0
冤大头->门小抠:10
冤大头账户余额:99990
门小抠账户余额:200010
Thread-1
门小抠->冤大头:20
门小抠账户余额:199990
冤大头账户余额:100010
Thread-1
门小抠->冤大头:20
门小抠账户余额:199970
冤大头账户余额:100030
Thread-0
冤大头->门小抠:10
冤大头账户余额:100020
门小抠账户余额:199980
Thread-0
冤大头->门小抠:10
冤大头账户余额:100010
门小抠账户余额:199990
Thread-1
门小抠->冤大头:20
门小抠账户余额:199970
冤大头账户余额:100030
Thread-0
冤大头->门小抠:10
冤大头账户余额:100020
门小抠账户余额:199980
Thread-1
门小抠->冤大头:20
门小抠账户余额:199960
冤大头账户余额:100040
Thread-0
冤大头->门小抠:10
冤大头账户余额:100030
门小抠账户余额:199970
Thread-1
门小抠->冤大头:20
门小抠账户余额:199950
冤大头账户余额:100050
Thread-0
冤大头->门小抠:10
冤大头账户余额:100040
门小抠账户余额:199960
Thread-1
门小抠->冤大头:20
门小抠账户余额:199940
冤大头账户余额:100060
这时,我们发现进程还在运行,但是控台停止输出了,它停在那里了。
 截图如下:
 
这时我们使用 jps 来查看一下java进程:
D:\open_source\MyBatis\MyThread\target\classes\demo>jps
23600
24676 Launcher
40244 Jps
23672 TransferAccount
然后输入 使用jstack来看详情:
D:\open_source\MyBatis\MyThread\target\classes\demo>jstack 23672
2023-02-26 18:37:57
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.351-b10 mixed mode):
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000001b4fc785800 nid=0x6530 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Thread-1" #13 prio=5 os_prio=0 tid=0x000001b4fc77f800 nid=0x887c waiting for monitor entry [0x000000467adff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at deadlock.TransferAccount.run(TransferAccount.java:20)
        - waiting to lock <0x000000076c5a4610> (a deadlock.Account)
        - locked <0x000000076c5a4658> (a deadlock.Account)
        at java.lang.Thread.run(Thread.java:750)
"Thread-0" #12 prio=5 os_prio=0 tid=0x000001b4fc77c800 nid=0x8c80 waiting for monitor entry [0x000000467acff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at deadlock.TransferAccount.run(TransferAccount.java:20)
        - waiting to lock <0x000000076c5a4658> (a deadlock.Account)
        - locked <0x000000076c5a4610> (a deadlock.Account)
        at java.lang.Thread.run(Thread.java:750)
......
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x000001b4fa208eb8 (object 0x000000076c5a4610, a deadlock.Account),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x000001b4fa208e08 (object 0x000000076c5a4658, a deadlock.Account),
  which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
        at deadlock.TransferAccount.run(TransferAccount.java:20)
        - waiting to lock <0x000000076c5a4610> (a deadlock.Account)
        - locked <0x000000076c5a4658> (a deadlock.Account)
        at java.lang.Thread.run(Thread.java:750)
"Thread-0":
        at deadlock.TransferAccount.run(TransferAccount.java:20)
        - waiting to lock <0x000000076c5a4658> (a deadlock.Account)
        - locked <0x000000076c5a4610> (a deadlock.Account)
        at java.lang.Thread.run(Thread.java:750)
Found 1 deadlock.
我们可以看到,其中Thread-0和Thread-1都已经处于BLOCK状态,发生了死锁。
导致死锁发生,必须同时满足四个条件:互斥、占有且等待、不可抢占、循环等待。
- 互斥:共享资源A和B只能被一个线程占用。
- 占有且等待:线程Thread-1 已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X。
- 不可抢占:其他线程不能强行抢占线程Thread-1 占有的资源。
- 循环等待:线程Thread-1 等待线程Thread-2 占有的资源,线程Thread-2等待Thread-1 占有的资源。
如何解决?
如果要解决死锁问题,只需要破坏其中一个条件,使其不满足即可。
 除了互斥(多线程的基础)之外,其他三个条件都可以考虑破坏。
 实际中遇到死锁,只能重启,如果还是死锁,要定位问题点,然后修复代码(破坏掉死锁必须满足的条件之一)后重新发布。
我们来尝试破坏占有且等待的方式来破坏死锁:
 新增加一个分配的类:Allocator,它有一个账户池。每个转账线程中要判断,账户池中是否已有此账户,如果没有可以转账,有的话就不转账。就可以避免共享资源同时存在于两个线程。
import java.util.ArrayList;
import java.util.List;
public class Allocator {
    private List<Object> list = new ArrayList<>();
    synchronized boolean apply(Object fromAccount,Object toAccount){
        if(list.contains(fromAccount)||list.contains(toAccount)){
            return false;
        }
        list.add(fromAccount);
        list.add(toAccount);
        return true;
    }
    synchronized void free(Object fromAccount,Object toAccount){
        list.remove(fromAccount);
        list.remove(toAccount);
    }
}
//相应的,TransAcount也要做修改:
public class TransferAccount implements Runnable {
    private Account fromAccount;
    private Account toAccount;
    private int amount;
    private Allocator allocator;
    public TransferAccount(Account fromAccount, Account toAccount, int amount, Allocator allocator) {
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.amount = amount;
        this.allocator = allocator;
    }
    @Override
    public void run() {
        while (true) {
        	//判断账户是否已经分配过
            if (allocator.apply(fromAccount, toAccount)) {
                try {
                    synchronized (fromAccount) {
                        synchronized (toAccount) {
                            if (fromAccount.getBalance() >= amount) {
                                fromAccount.debit(amount);
                                toAccount.credit(amount);
                            }
                            System.out.println(Thread.currentThread().getName());
                            System.out.println(fromAccount.getCountName() + "->" + toAccount.getCountName() + ":" + amount);
                            System.out.println(fromAccount.getCountName() + "账户余额:" + fromAccount.getBalance());
                            System.out.println(toAccount.getCountName() + "账户余额:" + toAccount.getBalance());
                        }
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }finally {
                    allocator.free(fromAccount,toAccount);
                }
            }
        }
    }
    public static void main(String[] args) {
        Account bigHead = new Account("冤大头", 100000);
        Account smallKill = new Account("门小抠", 200000);
        Allocator allocator = new Allocator();
        new Thread(new TransferAccount(bigHead, smallKill, 10, allocator)).start();
        new Thread(new TransferAccount(smallKill, bigHead, 20, allocator)).start();
    }
}
我们来避免第三个条件:使用Lock来替换掉 Synchronized。
 Synchronized加锁后,要等到资源释放,而且锁是不可抢占的。
 Lock中有个方法 tryLock,可以返回布尔值,如果返回fasle就不会进去。tryLock不会持续持有锁。
public class TransferAccount2 implements Runnable{
    private Account fromAccount;
    private Account toAccount;
    private int amount;
    private Lock fromAccountLock = new ReentrantLock();
    private Lock toAccountLock = new ReentrantLock();
    public TransferAccount2(Account fromAccount, Account toAccount, int amount) {
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.amount = amount;
    }
    @Override
    public void run() {
        while(true){
            //synchronized (fromAccount){
              if(fromAccountLock.tryLock()){
                if(toAccountLock.tryLock()){
                    if(fromAccount.getBalance()>=amount) {
                        fromAccount.debit(amount);
                        toAccount.credit(amount);
                    }
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(fromAccount.getCountName()+"->"+toAccount.getCountName()+":"+amount );
                    System.out.println(fromAccount.getCountName() +"账户余额:"+ fromAccount.getBalance());
                    System.out.println(toAccount.getCountName() +"账户余额:"+ toAccount.getBalance());
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Account bigHead = new Account("冤大头",100000);
        Account smallKill = new Account("门小抠",200000);
        new Thread(new TransferAccount2(bigHead,smallKill,10)).start();
        new Thread(new TransferAccount2(smallKill,bigHead,20)).start();
    }
}



















