Run the Main.main()
method seems like a deadlock has occurred.
I found out it can be fixed if replace notify()
with notifyAll()
.
But why?
Shouldn't the worst case always be called Lazy Thread to another Lazy Thread?
public class Main {
public static void main(String[] args) {
Table table = new Table(3);
new MakerThread("MakerThread-1", table, 8931415L).start();
new MakerThread("MakerThread-2", table, 314144L).start();
new MakerThread("MakerThread-3", table, 42131415L).start();
new EaterThread("EaterThread-1", table, 6313L).start();
new EaterThread("EaterThread-2", table, 8536313L).start();
new EaterThread("EaterThread-3", table, 35256313L).start();
new LazyThread("LazyThread-1", table).start();
new LazyThread("LazyThread-2", table).start();
new LazyThread("LazyThread-3", table).start();
new LazyThread("LazyThread-4", table).start();
new LazyThread("LazyThread-5", table).start();
new LazyThread("LazyThread-6", table).start();
new LazyThread("LazyThread-7", table).start();
}
}
public class Table {
private final String[] buffer;
private int tail;
private int head;
private int count;
public Table(int count) {
this.buffer = new String[count];
this.tail = 0;
this.head = 0;
this.count = 0;
}
public synchronized void put(String cake) throws InterruptedException {
while (count >= buffer.length) {
wait();
}
System.out.println(Thread.currentThread().getName() + " puts " + cake);
buffer[tail] = cake;
tail = (tail + 1) % buffer.length;
count++;
notify();
}
public synchronized String take() throws InterruptedException {
while (count <= 0) {
wait();
}
String cake = buffer[head];
head = (head + 1) % buffer.length;
count--;
System.out.println(Thread.currentThread().getName() + " takes " + cake);
notify();
return cake;
}
}
public class EaterThread extends Thread {
private final Random random;
private final Table table;
public EaterThread(String name, Table table, long seed) {
super(name);
this.random = new Random(seed);
this.table = table;
}
@Override
public void run() {
try {
while (true) {
String cake = table.take();
Thread.sleep(random.nextInt(1000));
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public class MakerThread extends Thread {
private final Random random;
private final Table table;
private static int id = 0;
public MakerThread(String name, Table table, long seed) {
super(name);
this.random = new Random(seed);
this.table = table;
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000));
String cake = " Cake No." + nextId() + " by " + getName() + " ]";
table.put(cake);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private static synchronized int nextId() {
return ++id;
}
}
public class LazyThread extends Thread {
private final Table table;
public LazyThread(String name, Table table) {
super(name);
this.table = table;
}
@Override
public void run() {
while (true) {
try {
synchronized (table) {
table.wait();
}
System.out.println(getName() + " is notified");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
You need notifyAll instead of notify. Otherwise, the a maker's notify could wake up another maker and put the whole thing into deadlock. Ditto for the lazies.
A better way to do this would be to use one lock for the makers and one lock for the lazies (takers) then you can just use notify when things are added or removed
public synchronized void put(String cake) throws InterruptedException {
while (count >= buffer.length) {
wait();
}
System.out.println(Thread.currentThread().getName() + " puts " + cake);
buffer[tail] = cake;
tail = (tail + 1) % buffer.length;
count++;
notifyAll();
}
public synchronized String take() throws InterruptedException {
while (count <= 0) {
wait();
}
String cake = buffer[head];
head = (head + 1) % buffer.length;
count--;
System.out.println(Thread.currentThread().getName() + " takes " + cake);
notifyAll();
return cake;
}