Избягване на само-блокиране

Java runtime system позволява нишката да получи отново монитор, който вече притежава (Мониторите са re-entrant). По този начин се избягва възможността за само-блокиране на дадена нишка върху монитор, който тя притежава.

 

public class Reentrant {
public static void main(String a[]){
Reentrant r = new Reentrant();
r.a();
}
public synchronized void a() {
b();
System.out.println("here I am, in a()");
}
public synchronized void b() {
System.out.println("here I am, in b()");
}
}
here I am, in b()
here I am, in a()

 

Взаимно блокиране (Deadlocks)

Взаимното блокиране е ситуация при която една или повече нишки очакват да се изпълни неизпълнимо условие. Най- често това се случва, когато всяка от нишките очаква другата да направи нещо.

Задачата за хранещите се философи е една от най-често използваните за илюстриране на проблема:

Алгоритъм - всеки от философите циклично

взима лявата вилица
взима дясната вилица
яде известно време
оставя двете вилици
мисли известно време

Алгоритъмът на всеки философ се реализира чрез отделна нишка. Ако дадена вилица е заета нишката се блокира до освобождаване на вилицата. Ситуация Deadlocks: всички философи са взели лявата вилица и очакват да вземат дясната, която обаче е взета от друг философ.

 

class Philosopher extends Thread{
String name;
Fork leftFork, rightFork;
Philosopher(String name,Fork left, Fork right){
this.name=name;
super.setName(name);
leftFork=left;
rightFork=right;
}
public void run(){
for(int i=0;i<2;i++){
leftFork.take(this);
System.out.println(name+" look for the second fork ");
try {
sleep((int)(Math.random()*2));
} catch (InterruptedException e){}

rightFork.take(this);
System.out.println(name+" eating ");
try {
sleep((int)(Math.random()*1000));
} catch (InterruptedException e){}
System.out.println(name+" poses the forks - thinking");
leftFork.pose();
rightFork.pose();
try {
sleep((int)(Math.random()*1000));
} catch (InterruptedException e1){}
}
System.out.println(name+" stop dinning");
}
public String toString(){
return name;
}
}
class Fork{
private boolean taken;
String name;
Fork(String name){
taken = false;
this.name=name;
}
synchronized void take(Philosopher p){
while(taken){
try{ wait(); }
catch(InterruptedException e){
System.err.println(e);
}
}
System.out.println(p+" take "+name);
taken = true;
}
synchronized void pose(){
taken = false;
notify();
}
boolean canTake(){
if (!taken)return true;
else return false;
}
public String toString(){
return name;
}
}
class Dinner{
public static void main(String arg[]){
int number=5;
Philosopher ph[]= new Philosopher[number];
Fork fk[]= new Fork[number];
for(int i=0;i<number;i++){
fk[i]= new Fork("fork "+i);
}
for(int i=0; i<number;i++){
ph[i] = new Philosopher("P"+(i+1),fk[i],fk[(i+1)%number]);
ph[i].start();
}
}
}
P1 take fork 0
P1 look for the second fork
P2 take fork 1
P2 look for the second fork
P3 take fork 2
P3 look for the second fork
P4 take fork 3
P4 look for the second fork
P5 take fork 4
P5 look for the second fork

Избягване на взаимното блокиране. Два подхода:

1. Подреждане на ресурсите

Най-често използвания подход за избягване на взаимно блокиране е да се въведе подреждане на заключваните обекти и заключването им да се извършва винаги в нарастващ (или в намаляващ) ред. Това няма да промени нищо в поведението на философите от Р1 до Р4 - винаги те ще започват с лявата вилица. Философ Р5 обаче ще трябва да започне с дясната вилица - ако тя вече е заета от философ Р1 - нишката ще се блокира без да взима ресурси и ще позволи на Р4 да вземе втората вилица. В този случай най-малко една от нишките Р1,Р2,Р3 или Р4 ще има ресурси да работи и взаимно блокиране няма да се получи

2. Промяна на поведението на нишките

Вторият възможен подход се основава на  "толерантност" на нишките - при него всеки философ ако констатира, че някоя вилица е блокирана (генериране на изключение на взетата вилица) се отказва от ползването и на двете вилици (ако е взел лявата я връща) и изчаква известно време преди да опита отново.
public class TakenExc extends Exception{
    TakenExc(String name){
        System.out.println(name);
    }
}

class Philosopher extends Thread{
    String name;
    Fork leftFork, rightFork;
    Philosopher(String name,Fork left, Fork right){
        this.name=name+"("+left.name+","+right.name+")";
        super.setName(name);
        leftFork=left;
        rightFork=right;
    }
    public void run(){
        for(int i=0;i<2;i++){
            do {
                try {
                    leftFork.take(this);
                }catch (TakenExc te) {
                    try {
                        sleep((int)(Math.random()*600));
                    } catch (InterruptedException e){} 
                    continue;
                }
                System.out.println(name+" look for the second fork ");
                try {
                    sleep((int)(Math.random()*2));
                } catch (InterruptedException e){} 

                try {
                    rightFork.take(this);
                }catch (TakenExc te) {
                    leftFork.pose();
                    System.out.println(name+" pose "+leftFork+" : waiting");
                    try {
                        sleep((int)(Math.random()*600));
                    } catch (InterruptedException e){}
                    continue;
                }
                break;
            }while(true);
            System.out.println(name+" eating ");
            try {
                sleep((int)(Math.random()*1000));
            } catch (InterruptedException e){}
            System.out.println(name+" poses the forks - thinking");
            leftFork.pose();
            rightFork.pose();
            try {
                sleep((int)(Math.random()*1000));
            } catch (InterruptedException e1){}
        }
        System.out.println(name+" stop dinning");
    }
    public String toString(){
        return name;
    }
}

class Fork{
    private boolean taken;
    String name;
    Fork(String name){
        taken = false;
        this.name=name;
    }
    synchronized void take(Philosopher p)throws TakenExc{
        if(taken){
            throw new TakenExc(name+" is taken");
        }
        System.out.println(p+" take "+name);
        taken = true;
    }
    synchronized void pose(){
        taken = false;
        notify();
    }
    boolean canTake(){
        if (!taken)return true;
        else return false;
    }
    public String toString(){
        return name;
    }
}

class Dinner{
    public static void main(String arg[]){
        int number=5;
        Philosopher ph[]= new Philosopher[number];
        Fork fk[]= new Fork[number];
        for(int i=0;i<number;i++){
            fk[i]= new Fork("fork "+i);
        }
        for(int i=0; i<number;i++){       
            ph[i] = new Philosopher("P"+(i+1),fk[i],fk[(i+1)%number]);
            ph[i].start();
        }       
    }
}


 

Контролни въпроси