Home > front end >  Java concurrent deadlock
Java concurrent deadlock

Time:05-07

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);
            }
        }
    }
}

Console output enter image description here

CodePudding user response:

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;
}

CodePudding user response:

As the doc says:

public final void notify()

... If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation ...

...the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object. ...

https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#notify()

You can absolutely implement this so that you notify() only one thread. It depends on when the locked object is released by the preceding thread. If one thread is notified but the resource is still bound to the notifying thread, the released thread goes back to the wait status and after that there is no thread being notified.

When you notifyall() waiting threads and when the first thread does not get the locked object (because still locked by the notifying thread) then the remaining awoken threads will try to catch it.

So, with many awoken threads there is a much higher possibility of the locked object being catched by one of them.

  • Related