Elliott LE GUEHENNEC -- Yorick GEOFFRE
Le patron lecteur/rédacteur présente le problème suivant : lorsqu'un lecteur lit la ressource, il la bloque et les rédacteurs ne peuvent pas la modifier. Or, dans notre configuration, il se peut que le Client
doive attendre alors qu'il essaie d'obtenir sa Patisserie
, mais il serait quand même en train de lire la file d'attente, empêchant ainsi le Patissier
de réapprovisioner la file; c'est un deadlock.
L'exclusion mutuelle se produit lorsqu'un Client
veut acheter une Patisserie
alors qu'aucune n'est disponible. Lorsque cela se produit, la Boulangerie
bloque le thread en attendant qu'un Patissier
dépose une Patisserie
dans la boulangerie, débloquant ainsi la demande du client. La Boulangerie
débloque le thread correspondant et retourne la Patisserie
désirée au client.
La Boulangerie
est donc le moniteur dans ce modèle, on l'utilise pour synchroniser toutes les opérations des Client
, des Patissier
, et la file d'attente.
Pour lancer des threads, on a créé une class ThreadWeaver. On peut lui faire passer des Runnable
avec addRunners()
, puis on leur assigne un thread chacun avec weave()
, avant de les lancer avec run()
. Avec recover()
, on attend que tous les threads se soient terminés, et avec termina()
, on interrompt tous les threads restants en affichant une erreur.
Ici, la ArrayBlockingQueue
devient le moniteur, car elle gère elle-même l'accès par exclusion mutuelle. Le client sera bloqué par la file, et attendra tant qu'il n'aura pas son code grâce à ArrayBlockingQueue.take();
.
Ca n'est pas une bonne solution, car s'il y a plus de producteurs que de consommateurs, les producteurs s'arrêtent de produire alors qu'il reste encore des consommateurs, bloquant ainsi des consommateurs qui n'ont pas acheté toutes les pâtisseries qu'ils voulaient.
L'inconvénient est que le main doit directement interagir avec la boulangerie comme s'il était un producteur particulier.
Si on essaie d'insérer un gâteau empoisonné alors que la queue est déjà pleine, cela peut causer des soucis.
public class Patisserie {
public Patisserie() {
}
}
public class Gateau extends Patisserie {
public static final Gateau GATEAU_EMPOISONNE = new Gateau();
public Gateau() {
}
}
public class Client implements Runnable {
protected final Boulangerie local;
public Client(Boulangerie b) {
local = b;
}
@Override
public void run() {
while(true) {
local.achete();
System.out.println("J'ai acheté ma patisserie");
try {
Thread.sleep(80);
} catch (InterruptedException e) {
break;
}
}
}
}
public class LimitedClient extends Client{
public LimitedClient(Boulangerie b) {
super(b);
}
@Override
public void run() {
while(true) {
local.achete();
System.out.println("J'ai acheté ma patisserie - Limited");
try {
Thread.sleep(80);
} catch (InterruptedException e) {
break;
}
}
}
}
public class Patissier implements Runnable {
public AtomicBoolean shouldRun = new AtomicBoolean(true);
private final Boulangerie local;
public Patissier(Boulangerie b) {
local = b;
}
@Override
public void run() {
while(shouldRun.get()) {
local.depose(new Patisserie());
System.out.println("J'ai produit ma patisserie");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
}
}
}
public class PatissierSuicidal extends Patissier {
public AtomicBoolean shouldRun = new AtomicBoolean(true);
public PatissierSuicidal(Boulangerie b) {
super(b);
}
@Override
public void run() {
while(shouldRun.get()) {
if(!local.depose(new Patisserie())){
shouldRun.set(false);
}
System.out.println("J'ai produit ma patisserie");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
}
}
}
public class Boulangerie {
private final ArrayList<Patisserie> sweets = new ArrayList<>();
public synchronized void depose(Patisserie p) {
this.notify();
sweets.add(p);
}
public synchronized Patisserie achete() {
while(sweets.size() == 0) {
try {this.wait();} catch (InterruptedException ignored) {}}
Patisserie pat = sweets.get(0);
sweets.remove(pat);
return pat;
}
public void getStock() {
return sweets.size();
}
}
public class BoulangerieThreadSafe extends Boulangerie{
private final BlockingQueue<Patisserie> sweets = new ArrayBlockingQueue<Patisserie>(10);
@Override
public boolean depose(Patisserie p) {
try{
if(sweets.remainingCapacity() <= 0) return false;
sweets.put(p);
}catch(InterruptedException ignored){return false;}
return true;
}
@Override
public Patisserie achete() {
try{return sweets.take();} catch (InterruptedException ignored) {}
return null;
}
@Override
public int getStock() {
return sweets.size();
}
}
public class ThreadWeaver {
private final List<Runnable> runners = new ArrayList<Runnable>();
private final List<Thread> managed = new ArrayList<Thread>();
public void addRunners(Runnable... addedRunners){
runners.addAll(Arrays.stream(addedRunners).toList());
}
public void weave(){
for(Runnable r: runners){
managed.add(new Thread(r));
}
}
public void run(){
for(Thread t : managed){
t.start();
}
}
public void recover(){
for(Thread t : managed){
try {
t.join();
}catch(InterruptedException ie){
System.out.println(ie.getMessage());
}
}
termina();
}
public void recover(Long timeout){
for(Thread t : managed){
try {
t.join(timeout);
}catch(InterruptedException ie){
System.out.println(ie.getMessage());
}
}
termina();
}
public void termina(){
for(Thread t : managed){
if(t.isAlive()) {
System.out.println("Thread " + t.getName() + " has not stopped being cleaned up");
t.interrupt();
}
}
managed.clear();
runners.clear();
}
}
public class Main {
public static void main(String[] args) {
ThreadWeaver tw = new ThreadWeaver();
Boulangerie b = new Boulangerie();
Patissier p = new Patissier(b);
tw.addRunners(p, new Client(b));
tw.weave();
tw.run();
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
p.shouldRun.set(false);
tw.recover(100L);
}
}
public class Main2 {
public static void main(String[] args) {
ThreadWeaver tw = new ThreadWeaver();
Boulangerie b = new BoulangerieThreadSafe();
Patissier p = new Patissier(b);
tw.addRunners(p, new Client(b));
tw.weave();
tw.run();
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
p.shouldRun.set(false);
tw.recover(100L);
}
}
public class Main3A {
public static void main(String[] args) {
ThreadWeaver tw = new ThreadWeaver();
Boulangerie b = new BoulangerieThreadSafe();
Patissier p = new Patissier(b);
tw.addRunners(p, new LimitedClient(b));
tw.weave();
tw.run();
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
p.shouldRun.set(false);
tw.recover();
}
}
public class Main3B {
public static void main(String[] args) {
ThreadWeaver tw = new ThreadWeaver();
Boulangerie b = new BoulangerieThreadSafe();
Patissier p = new Patissier(b);
Client c1 = new Client(b);
tw.addRunners(p, c1);
tw.weave();
tw.run();
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
b.depose(Gateau.GATEAU_EMPOISONNE);
tw.recover();
}
}