什么是死锁?
两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。
例如,如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生了。线程1永远得不到B,线程2也永远得不到A,并且它们永远也不会知道发生了这样的事情。为了得到彼此的对象(A和B),它们将永远阻塞下去。这种情况就是一个死锁。
public class DeadLock implements Runnable{
public int flag = 1;
private static Object object1 = new Object();
private static Object object2 = new Object();
@Override
public void run() {
System.out.println("The Flag is :"+flag);
if(flag == 1){
synchronized (object1){
try {
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
synchronized (object2){
System.out.println("The object2 is locked!");
}
}
} else if(flag == 2){
synchronized (object2){
try {
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
synchronized (object1){
System.out.println("The object1 is locked!");
}
}
}
}
public static void main(String[] args) {
DeadLock deadLock1 = new DeadLock();
DeadLock deadLock2 = new DeadLock();
deadLock1.flag = 1;
deadLock2.flag = 0;
//deadLock1,deadLock2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
//deadLock2的run()可能在deadLock1的run()之前运行
new Thread(deadLock1).start();
new Thread(deadLock2).start();
}
}
死锁的四个条件
1、互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用完释放。
2、请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3、不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4、循环等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{A,B,C,···,Z} 中的A正在等待一个B占用的资源;B正在等待C占用的资源,……,Z正在等待已被A占用的资源。
预防死锁
预防死锁的四种方法
破坏互斥条件:使资源同时访问而非互斥使用,就没有进程会阻塞在资源上,从而不发生死锁。
破坏请求和保持条件:采用静态分配的方式,静态分配的方式是指进程必须在执行之前就申请需要的全部资源,且直至所要的资源全部得到满足后才开始执行,只要有一个资源得不到分配,也不给这个进程分配其他的资源。
破坏不剥夺条件:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源,但是只适用于内存和处理器资源。
破坏循环等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行。
设置加锁顺序
如果两个线程(A和B),当A线程已经锁住了Z,而又去尝试锁住X,而X已经被线程B锁住,线程A和线程B分别持有对应的锁,而又去争夺其他一个锁(尝试锁住另一个线程已经锁住的锁)的时候,就会发生死锁,如下图:
两个线程试图以不同的顺序来获得相同的锁,如果按照相同的顺序来请求锁,那么就不会出现循环的加锁依赖性,因此也就不会产生死锁,每个需要锁Z和锁X的线程都以相同的顺序来获取Z和X,那么就不会发生死锁了,如下图所示:
这样死锁就永远不会发生。 针对两个特定的锁,可以尝试按照锁对象的hashCode值大小的顺序,分别获得两个锁,这样锁总是会以特定的顺序获得锁,我们通过设置锁的顺序,来防止死锁的发生,在这里我们使用System.identityHashCode方法来定义锁的顺序,这个方法将返回由Obejct.hashCode 返回的值,这样就可以消除死锁发生的可能性。
import javax.naming.InsufficientResourcesException;
class Money{
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public void addCount(int count){
this.count += count;
}
public void subCount(int count){
this.count -= count;
}
public boolean compareTo(int tranCount){
return tranCount>count?false:true;
}
}
public class DeadLockTest {
private static final Object lock = new Object();
// 这个方法容易发生动态的锁顺序死锁,这个是错误的方法
public void tranForMoney(Money fromMoney,Money toMoney,int money) throws InsufficientResourcesException {
synchronized (fromMoney){
synchronized (toMoney){
if(fromMoney.compareTo(money)){
throw new InsufficientResourcesException();
}else {
fromMoney.subCount(money);
toMoney.addCount(money);
}
}
}
}
// 通过锁顺序来避免死锁——正确方法
public void tranFerMoney2(Money fromMoney,Money toMoney,int money) throws InsufficientResourcesException {
class Helper{
public void tranfer() throws InsufficientResourcesException {
if(fromMoney.compareTo(money)){
throw new InsufficientResourcesException();
}else {
fromMoney.subCount(money);
toMoney.addCount(money);
}
}
}
int fromHashCode = System.identityHashCode(fromMoney);
int toHashCode = System.identityHashCode(toMoney);
if(fromHashCode < toHashCode){
synchronized (fromMoney){
synchronized (toMoney){
new Helper().tranfer();
}
}
}else if(fromHashCode > toHashCode){
synchronized (toMoney){
synchronized (fromMoney){
new Helper().tranfer();
}
}
}else {
// 设置时赛锁
synchronized (lock){
synchronized (fromMoney){
synchronized (toMoney){
new Helper().tranfer();
}
}
}
}
}
}
在极少数情况下,两个对象可能拥有两个相同的散列值,此时必须通过某种任意的方法来决定锁的顺序,否则可能又会重新引入死锁。为了避免这种情况,可以使用 “加时(Tie-Breaking))”锁 ,这获得这两个Account锁之前,从而消除了死锁发生的可能性
避免死锁之银行家算法
银行家算法概述
银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,这就需要引入安全序列来进行判断资源分配是否合理。安全序列是指一个进程序列比如从P1到Pn是安全的,即对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和。如果存在有一个由系统中所有进程构成的安全序列P1…Pn,则系统处于安全状态系统处于安全状态则说明系统的资源分配合理,进程运行的过程中不存在死锁,若分配不会导致系统进入不安全状态,则分配,否则等待。
实现银行家算法过程
首先执行资源预分配算法,设Request[i] 是进程Pi的请求向量,如果Request[i,j]=K,表示进程需要K个Rj类型的资源。当Pi发出资源请求后,系统按下述步骤进行检查:
(1)判断进程请求的资源是否超过进程以后还需要的资源矩阵, 即 Request[i,j]<= Need[i,j]若条件不成立,则认为系统无法分配,因为它需要的最大资源数已超过它所宣布的最大值。
(2)判断进程请求资源是否超过系统现有的资源, 即Request[i,j]<= Available[j],如果条件不成立,则系统现有的资源无法满足当前进程请求的资源数,进程需要被设置为等待状态。
(3)系统试探着把资源分配给进程Pi, 并修改下面数据结构中的数值:
Available[j]= Available[j]- Request[i,j];
Allocation[i,j]= Allocation[i,j]+ Request[i,j];
Need[i,j]= Need[i,j]- Request[i,j];
(4)系统执行安全性算法, 检查此次资源分配后,系统是否处于安全状态。若安全,才正式将资源分配给进程Pi,以完成本次分配;否则,将本次的试探分配作废,恢复原来的资源分配状态,让进程Pi等待。
进程资源预分配完成后,系统需要执行的安全性算法,安全性算法执行的具体步骤如下:
(1)设置两个向量:①工作向量Work:它表示系统可提供给进程继续运行所需要的各类资源数目,它含有m个元素,在执行安全算发开始时,Work=Available;②Finish:它表示系统是否有足够的资源分配给进程,使之运行完成。开始时先做Finish[i]=false;当有足够资源分配给进程时,再令Finish[i]=true。
(2)从进程集合中找到一个能满足下述条件的进程:Finish[i]=false; Need[i,j] <= Work[j];若找到,执行步骤(3),否则,执行步骤(4)。
(3)当进程Pi获得资源后,可顺利执行,直至完成,并释放出分配给它的资源,故应执行:
Work[j]= Work[i]+ Allocation[i,j];
Finish[i]=true;
(4)如果所有进程的Finish[i]=true都满足,则表示系统处于安全状态;否则,系统处于不安全状态。
BankerTest.class
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BankerTest {
private static List<Integer> Available = new ArrayList<>();
private static List<List<Integer>> Max = new ArrayList<>();
private static List<List<Integer>> Allocation = new ArrayList<>();
private static List<List<Integer>> Need = new ArrayList<>();
// 银行家算法
public static void MockBanker(RequestData data) {
// 获取进程ID
int id = data.getId();
boolean Error = false ,Wait = false;
int len = data.getRequestList().size();
// 获取资源请求列表
List<Integer> request = data.getRequestList();
for (int i = 0; i < len; i++) {
if(request.get(i) > Need.get(id).get(i)){ // 判断 Requesti[j] ≤ Need[i,j]
Error = true;
}else if(request.get(i) > Available.get(i)){ // 判断 Requesti[j] ≤ Available[j]
Wait = true;
}
}
if(Error){
System.out.println("进程P"+id+"申请资源"+request+"无法分配");
return;
}else if(Wait){
System.out.println("进程P"+id+"申请资源"+request+"无法满足");
return;
}
// 尝试资源预分配
// 当前系统中已经分配的资源
List<Integer> currentAllocation = Allocation.get(id);
// 当前进程仍需要的资源
List<Integer> currentNeed = Need.get(id);
int length = currentAllocation.size();
for (int i = 0; i < length; i++) {
Available.set(i,Available.get(i)-request.get(i));
currentAllocation.set(i,currentAllocation.get(i)+request.get(i));
currentNeed.set(i,currentNeed.get(i)-request.get(i));
}
Allocation.set(id,currentAllocation);
Need.set(id,currentNeed);
// 安全性检查
if(!SourceSecurityCheck()){
// 如果安全性检查不通过,则执行资源回滚操作
for (int i = 0; i < length; i++) {
Available.set(i,Available.get(i)+request.get(i));
currentAllocation.set(i,currentAllocation.get(i)-request.get(i));
currentNeed.set(i,currentNeed.get(i)+request.get(i));
}
Allocation.set(id,currentAllocation);
Need.set(id,currentNeed);
System.out.println("进程P"+id+"请求的系统资源:"+request+"后,无法保证进程的安全性!");
}else { // 安全性检查通过,成功分配资源。
System.out.println("进程P"+id+"请求的系统资源:"+request+"分配成功!");
}
}
// 安全性检查
public static boolean SourceSecurityCheck(){
// 初始化Work和Finish矩阵
List<Integer> Work = new ArrayList<>();
List<Boolean> Finish = new ArrayList<>();
Work.addAll(Available);
int max = Max.size();
for (int i = 0; i < max; i++) {
Finish.add(i,false);
}
int FinishCount; //判断资源分配完成多少个
for (FinishCount = 0;FinishCount < max;FinishCount++){
for (int i = 0; i < max; i++) {
if(!Finish.get(i)){// 判断没完成系统安全性检查的进程
List<Integer> currentNeed = Need.get(i);
int j;
for (j = 0; j < Work.size(); j++) {
if(currentNeed.get(j) > Work.get(j)){
break;
}
}
if(j == Work.size()){
List<Integer> currentAllocation = Allocation.get(i);
for (j = 0; j < Work.size(); j++) {
Work.set(j,Work.get(j)+currentAllocation.get(j));
}
Finish.set(i,true);
System.out.println("进程P"+i+"执行完毕");
}
}
}
}
for(int id = 0; id < max; id++){
if(!Finish.get(id)){
return false;
}
}
return true;
}
public static void printSourceAllocation(){
System.out.println("Max\t\t\tAllocation\tNeed");
for (int i = 0; i < Max.size(); i++) {
System.out.println(Max.get(i)+"\t"+Allocation.get(i)+"\t"+Need.get(i));
}
System.out.println("当前可用资源为:"+Available);
}
public static void main(String[] args){
Integer[][] maxArray = {{7,5,3}, {3,2,2}, {9,0,2}, {2,2,2}, {4,3,3}};
for (Integer[] tmpArray : maxArray) {
Max.add(Arrays.asList(tmpArray));
}
Integer[][] allocationArray = {{0,1,0}, {2,0,0}, {3,0,2}, {2, 1, 1}, {0,0,2}};
for (Integer[] tmpArray : allocationArray) {
Allocation.add(Arrays.asList(tmpArray));
}
int len = maxArray.length;
int temp = maxArray[0].length;
Integer[][] needArray = new Integer[len][temp];
for (int i = 0; i < len; i++) {
for (int j = 0; j < temp; j++) {
needArray[i][j] = maxArray[i][j]-allocationArray[i][j];
}
}
for (Integer[] tmpArray : needArray) {
Need.add(Arrays.asList(tmpArray));
}
Integer[] availableArray = {3,3,2};
Available = Arrays.asList(availableArray);
System.out.println("第一问:");
printSourceAllocation();
System.out.println("安全性检查结果为:"+SourceSecurityCheck());
System.out.println();
System.out.println("第二问:");
RequestData request = new RequestData();
request.setId(1);
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(0);
list.add(2);
request.setRequestList(list);
printSourceAllocation();
MockBanker(request);
System.out.println();
System.out.println("第三问:");
RequestData request1 = new RequestData();
request1.setId(3);
List<Integer> list1 = new ArrayList<>();
list1.add(3);
list1.add(3);
list1.add(0);
request1.setRequestList(list1);
printSourceAllocation();
MockBanker(request1);
System.out.println();
System.out.println("第四问: ");
RequestData request2 = new RequestData();
request2.setId(0);
List<Integer> list2 = new ArrayList<>();
list2.add(0);
list2.add(2);
list2.add(0);
request2.setRequestList(list2);
printSourceAllocation();
MockBanker(request2);
System.out.println();
}
}
RequestData.class
import java.util.List;
public class RequestData{
private int id;
private List<Integer> requestList;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<Integer> getRequestList() {
return requestList;
}
public void setRequestList(List<Integer> requestList) {
this.requestList = requestList;
}
}