概述
线程死锁
- 什么是死锁
- 一个必然死锁的程序
- 死锁发生的四个必要条件
- 实际生产中死锁问题
- 银行转账问题
- 使获取锁的顺序一致解决银行转账问题
- 哲学家就餐问题以及对应的解决方法
- 解决哲学家就餐问题思路
- 其它线程活跃性问题
- 活锁
- 饥饿
什么是死锁
线程1持有资源A,线程2持有资源B。这时如果线程1去请求资源B,线程2去请求资源A。由于资源A和资源B都已经被其它线程所持有,导致线程1和线程2一直无法获取到想要的资源,陷入无限等待状态。
一个必然死锁的程序
下面通过一段代码来演示下:
/**
* 必然死锁的例子
*/
public class MustDeadLock implements Runnable {
int state = 1;
static Object lock1 = new Object();
static Object lock2 = new Object();
public static void main(String[] args) {
MustDeadLock mustDeadLock = new MustDeadLock();
MustDeadLock mustDeadLock1 = new MustDeadLock();
mustDeadLock.state = 0;
new Thread(mustDeadLock).start();
new Thread(mustDeadLock1).start();
}
@Override
public void run() {
try {
if (state == 1) {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName()+"获取到lock1");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"准备获取lock2");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName()+"获取到lock2");
}
}
}
if (state == 0) {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName()+"获取到lock2");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"准备获取lock1");
synchronized (lock1) {
System.out.println(Thread.currentThread().getName()+"获取到lock1");
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
Thread-0获取到lock2
Thread-1获取到lock1
Thread-1准备获取lock2
Thread-0准备获取lock1
Thread-0获取到lock2,Thread-1获取到lock1。这时当Thread-1准备获取lock2时,由lock2被Thread-0持有,所以Thread-1就陷入了无限等待状态。Thread-0也一样一直等待获取lock1。
死锁发生的四个必要条件
通过以上例子,我们分析下死锁发生的四个必要条件。
1:互斥条件。无论时lock1 还是lock2,都只能同时被一个线程所持有。
2:持有并请求。Thread-0持有了一个资源lock2,这时它又去请求另外一个资源。
3:不剥夺条件。Thread-0持有的资源lock2只能由它自己去释放,线程不能释放不是由自己持有的资源。
4:环路等待。Thread-0获取到lock2,去请求lock1,Thread-1获取到lock1,去请求lock2。Thread-0在等待Thread-1持有的资源,Thread-1在等待Thread-0持有的资源。Thread-0 --》Thread-1 --》Thread-0。
如果发生死锁,以上这四个条件缺一不可。
实际生产中死锁问题
银行转账问题
有两个用户张三和李四,张三的账户余额有500元,李四的账户余额有500元。在同一时间张三向李四的账户转200元,李四向张三的账户转200元,互相转了100次。以下是具体实现。
/**
* 模拟两个人转账导致线程死锁问题
*/
public class TransferMoney implements Runnable {
Acount a;
Acount b;
int amount;
public TransferMoney(Acount a, Acount b, int amount) {
this.a = a;
this.b = b;
this.amount = amount;
}
public static void main(String[] args) {
Acount acount = new Acount(500);
Acount acount1 = new Acount(500);
TransferMoney transferMoney = new TransferMoney(acount, acount1, 200);
TransferMoney transferMoney2 = new TransferMoney(acount1, acount, 200);
for (int i = 0; i < 100; i++) {
new Thread(transferMoney).start();
new Thread(transferMoney2).start();
}
}
@Override
public void run() {
try {
transfer(a, b, amount);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void transfer(Acount a, Acount b, int amount) throws InterruptedException {
synchronized (a) {
synchronized (b) {
if (a.money < amount) {
System.out.println("账户余额不足,转账失败");
}
a.money -= amount;
b.money += amount;
System.out.println("转账成功");
}
}
}
static class Acount {
int money;
public Acount(int money) {
this.money = money;
}
}
}
执行结果:
互相转账100次,总共应该打印200次转账结果。但只执行了两次程序就没有执行下去了。下面分析下导致这个问题的原因。
张三向李四的转账,必须先获取到张三账户和李四账户两把锁,以防止转账期间其它线程对账户进行操作。如果发生这种情况:一个线程持有了张三账户,另外一个线程持有了李四账户,这时两个线程去请求另一个账户锁的时候就会请求不到。两个线程陷入无限等待状态,同时其它想要获取这两个账户锁的线程也会一直等待。
使获取锁的顺序一致解决银行转账问题
我们可以使获取锁的顺序一致来解决上面银行转账问题。上面问题导致的原因之一是,不同的线程获取锁的顺序不同。张三向李四转账,先获取张三账户锁,再获取李四账户锁;李四向张三转账,先获取李四账户锁,再获取张三账户锁,这样就会形成一个环路:线程1(张三)-》线程2(李四)-》线程1(张三)。所以我们只要破坏这个环路,无论是张三向李四转账还是李四向张三转账,它们获取锁的顺序都是一致的,就可以解决死锁问题。下面是具体实现。
/**
* 改变获取锁的顺序解决银行转账死锁问题。
*/
public class ChangeOrderDealTransferMoney implements Runnable {
TransferMoney.Acount a;
TransferMoney.Acount b;
int amount;
Object lock = new Object();
public ChangeOrderDealTransferMoney(TransferMoney.Acount a, TransferMoney.Acount b, int amount) {
this.a = a;
this.b = b;
this.amount = amount;
}
public static void main(String[] args) {
TransferMoney.Acount acount = new TransferMoney.Acount(500);
TransferMoney.Acount acount1 = new TransferMoney.Acount(500);
ChangeOrderDealTransferMoney transferMoney = new ChangeOrderDealTransferMoney(acount, acount1, 200);
ChangeOrderDealTransferMoney transferMoney2 = new ChangeOrderDealTransferMoney(acount1, acount, 200);
for (int i = 0; i < 1000; i++) {
new Thread(transferMoney).start();
new Thread(transferMoney2).start();
}
}
@Override
public void run() {
// 根据hash值来判断获取锁的顺序,如果有hash冲突,加一个竞争锁。
if (a.hashCode() > b.hashCode()) {
synchronized (a) {
synchronized (b) {
transfer(a, b, amount);
}
}
}
if (a.hashCode() < b.hashCode()) {
synchronized (b) {
synchronized (a) {
transfer(a, b, amount);
}
}
} else {
synchronized (lock) {
transfer(a, b, amount);
}
}
}
public static void transfer(TransferMoney.Acount a, TransferMoney.Acount b, int amount) {
if (a.money < amount) {
System.out.println("账户余额不足,转账失败");
}
a.money -= amount;
b.money += amount;
System.out.println("转账成功");
}
}
执行结果:
通过账户的hash值来判断获取锁的顺序,这样不论是张三向李四转账还是李四向张三转账,获取锁的顺序都一致。如果有hash冲突的情况,可以加一个竞争锁来保证线程安全。在实际生产中可以根据索引来判断获取锁的顺序。
哲学家就餐问题以及对应的解决方法
有五位哲学家在一起就餐,每位哲学家左右两边只有一个刀叉。只有拿起刀叉的时候才可以就餐。假设每个哲学家先拿左手边的餐具,再拿右手边的餐具,就完餐后就思考,思考完后又就餐,会发生什么情况。下面用代码演示一下:
/**
* 哲学家就餐问题演示。
*/
public class DiningPhilosopher implements Runnable {
Object knife;
Object cross;
public DiningPhilosopher(Object knife, Object cross) {
this.knife = knife;
this.cross = cross;
}
public static void main(String[] args) {
Object[] tableware = new Object[5];
for (int i = 0; i < tableware.length; i++) {
tableware[i] = new Object();
}
DiningPhilosopher[] diningPhilosophers = new DiningPhilosopher[5];
for (int i = 0; i < diningPhilosophers.length; i++) {
diningPhilosophers[i] = new DiningPhilosopher(tableware[i % 4], tableware[(i + 1) % 4]);
new Thread(diningPhilosophers[i]).start();
}
}
@Override
public void run() {
while (true) {
doSomething("thinking");
synchronized (knife) {
synchronized (cross) {
doSomething("eating");
doSomething("put down cross");
}
doSomething("put down knife");
}
}
}
private void doSomething(String str) {
System.out.println(Thread.currentThread().getName() + " " + str);
}
}
执行结果:
可以看到线程陷入了死锁。
解决哲学家就餐问题思路
解决哲学家就餐问题的方法有很多,从破坏死锁的四个必要条件去考虑。
1:从互斥条件考虑。餐具只能被一个人持有,所以这个条件是无法破坏的。
2:持有并请求。哲学家就餐是先拿左手边的餐具,再去拿右手边的餐具。可以让则学家同时去拿左手边的餐具和右手边的餐具。
3:不可剥夺。拿到餐具后,只有自己就餐后才放下。如果有一个服务员,可以提醒哲学家在还未就餐时放下餐具,也可以解决此问题。
4:环路等待。破坏环路等待有两种方式,一是改变一个哲学家拿餐具的顺序。第二:发餐牌,五个人发四张餐牌,只有拿到餐牌的哲学家才能就餐。
以上就是解决则学家就餐问题的一些思路。
其它线程活跃性问题
活锁
线程一直做一些无意义的事情
饥饿
线程一直得不到CPU的调度
最后
以上就是还单身书本为你收集整理的Java线程死锁什么是死锁实际生产中死锁问题其它线程活跃性问题的全部内容,希望文章能够帮你解决Java线程死锁什么是死锁实际生产中死锁问题其它线程活跃性问题所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复