[死记硬背]

死锁是两个或更多进程在执行过程中由于竞争资源或相互通信而产生的阻塞现象,没有外力就无法推进。此时系统处于死锁状态,或者系统出现死锁。这种相互等待的过程称为死锁过程。

僵局产生的四个必要条件:互不相容条件、不可转让条件、请求和保留条件以及循环等待条件。

解决死锁的方法:调整请求锁定范围,调整请求锁定顺序,禁用显示的解锁,用信号量控制,防止死锁的方针。

[答案分析]

实现死锁的代码

这是我进入阿里面试的一个试题。可以写下下面的代码。

public class demo 3 implements runnable {

私有字符串标记;

private static object lock 1=new object();

private static object lock 2=new object();

Public Demo3(字串标签){

=标签;

}

@Override

Public void run() {

I(' a '){

Synchronized(锁定1) {

Try {

Sy('当前线程:' T())。getName()“进入lock1并运行”);

t(2000);

} catch (InterruptedException e) {

e . printstacktrace();

}

Synchronized (lock2) {

Sy('当前线程:' T())。getName()“进入lock2并运行”);

}

}

}

I(' b '){

Synchronized (lock2) {

Try {

Sy('当前线程:' T())。getName()“进入lock2并运行”);

t(2000);

} catch (InterruptedException e) {

e . printstacktrace();

}

Synchronized(锁定1) {

Sy('当前线程:' T())。getName()“进入lock1并运行”);

}

}

}

}

public static void main(string[]args){

de mo3 D1=new de mo3(' a ');

de mo3 D2=new de mo3(' b ');

Thread t1=new Thread(d1,' t1 ');

Thread T2=new Thread(d2,' t 2 ');

();

();

}

}解决四个必要条件

#互斥条件

资源只能在一个进程中使用。

#不可转让的条件

如果某个过程占用了资源,就只能释放他一个人。

#请求和保留条件

在某个课程之前申请了资源,我还想再次申请资源。以前的资源由我占用,别人不要动。如果我不想用,就解开它。

#循环大气条件

一定有光环在互相等待。死锁的解决方案代码演示

申请锁定范围调整计划:

Public class TestClass {

public void method(test class clazz){

sy(' test class method in ');

同步(this){

//do something

}

clazz . method 2();

sy(' test class method out ');

}

public synchronized void method 2(){

Sy('TestClass method2

4;);
}
}
//上面代码原来锁是加在方法上的,现在改为在方法内的一部分,这样在使用第二个锁时本身的锁已经释放了。如果减小锁的申请范围可以避免锁的申请发生闭环的话,那么就可以避免死锁。

调整申请锁的顺序实现方案:

public class Account {
private int id; // 主键
private String name;
private double balance;
public void transfer(Account from, Account to, double money){
i() > ()){
synchronized(from){
synchronized(to){
// transfer
}
}
}else{
synchronized(to){
synchronized(from){
// transfer
}
}
}
}
public int getId() {
return id;
}
}
//在有些情况下是不允许我们调整锁的范围的,比如银行转账的场景下,我们必须同时获得两个账户上的锁,才能进行操作,两个锁的申请必须发生交叉。这时要想打破死锁闭环,必须调整锁的申请顺序,总是以相同的顺序来申请锁,比如总是先申请 id 大的账户上的锁 ,然后再申请 id 小的账户上的锁,这样就无法形成导致死锁的那个闭环。
//这样的话,即使发生了两个账户比如 id=1的和id=100的两个账户相互转账,因为不管是哪个线程先获得了id=100上的锁,另外一个线程都不会去获得id=1上的锁(因为他没有获得id=100上的锁),只能是哪个线程先获得id=100上的锁,哪个线程就先进行转账。这里除了使用id之外,如果没有类似id这样的属性可以比较,那么也可以使用对象的hashCode()的值来进行比较。

不使用显示的去锁,我们用信号量去控制

import java.u;
import java.u;
import java.u;

public class UnLockTest {
public static String obj1 = "obj1";
public static final Semaphore a1 = new Semaphore(1);
public static String obj2 = "obj2";
public static final Semaphore a2 = new Semaphore(1);

public static void main(String[] args) {
LockAa la = new LockAa();
new Thread(la).start();
LockBb lb = new LockBb();
new Thread(lb).start();
}
}
class LockAa implements Runnable {
public void run() {
try {
Sy(new Date().toString() + " LockA 开始执行");
while (true) {
if (1, TimeUnit.SECONDS)) {
Sy(new Date().toString() + " LockA 锁住 obj1");
if (1, TimeUnit.SECONDS)) {
Sy(new Date().toString() + " LockA 锁住 obj2");
T(60 * 1000); // do something
}else{
Sy(new Date().toString() + "LockA 锁 obj2 失败");
}
}else{
Sy(new Date().toString() + "LockA 锁 obj1 失败");
}
UnLockTe(); // 释放
UnLockTe();
T(1000); // 马上进行尝试,现实情况下do something是不确定的
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockBb implements Runnable {
public void run() {
try {
Sy(new Date().toString() + " LockB 开始执行");
while (true) {
if (1, TimeUnit.SECONDS)) {
Sy(new Date().toString() + " LockB 锁住 obj2");
if (1, TimeUnit.SECONDS)) {
Sy(new Date().toString() + " LockB 锁住 obj1");
T(60 * 1000); // do something
}else{
Sy(new Date().toString() + "LockB 锁 obj1 失败");
}
}else{
Sy(new Date().toString() + "LockB 锁 obj2 失败");
}
UnLockTe(); // 释放
UnLockTe();
T(10 * 1000); // 这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出。

避免死锁方针

a:避免嵌套封锁:这是死锁最主要的原因的,如果你已经有一个资源了就要避免封锁另一个资源。如果你运行时只有一个对象封锁,那是几乎不可能出现一个死锁局面的。
b:只对有请求的进行封锁:你应当只想你要运行的资源获取封锁.如果我们只对它所属领域中的一个感兴趣,那我们应当封锁住那个特殊的领域而并非完全的对象。
c:避免无限期的等待:如果两个线程正在等待对象结束,无限期的使用线程加入,如果你的线程必须要等待另一个线程的结束,若是等待进程的结束加入最好准备最长时间。

【温馨提示】

点赞+收藏文章,关注我并私信回复【面试题解析】,即可100%免费领取楼主的所有面试题资料!

相关推荐