Правильная синхронизация с помощью Lock в Java

Доброго времени суток всем. Помогите, пожалуйста, советом. Нужно решить задачу. У меня есть класс с main методом:

public class ThreadExample {
public static void main(String [] args) {
    Foo foo = new Foo();

    CompletableFuture.runAsync(() -> {
            foo.first(new Thread());
    });

    CompletableFuture.runAsync(() -> {
            foo.second(new Thread());
    });

    CompletableFuture.runAsync(() -> {
            foo.third(new Thread());
    });
}

}

и есть класс:

public class Foo {

Lock lock = new ReentrantLock();

public void first(Runnable r) {
    lock.lock();
    System.out.print("first");
    lock.unlock();

}

public void second(Runnable r) {
    lock.lock();
    System.out.print("second");
    lock.unlock();
}

public void third(Runnable r) {
    lock.lock();
    System.out.print("third");
    lock.unlock();
}

}

Мне нужно, чтобы методы класса Foo вызывались синхронизированно. Так как я только новичок, у меня есть 100% подозрение, что я делаю что-то не так. Подскажите, пожалуйста, как правильно сделать так, чтобы если, допустим в одном методе класса Foo написать Thread.sleep(); другие методы дожидались его.


Ответы (1 шт):

Автор решения: Serodv

Интерфейс Lock содержит метод newCondition(), который возвращает объект интерфейса Condition реализующий высокоуровневые аналоги методов wait/notify/notifyAll класса Object. Как было отмечено в комментарии, с помощью этих методов можно реализовать ожидание и уведомление потоков для организации необходимого порядка исполнения методов.

class Foo {
    private Lock lock = new ReentrantLock();
    private Condition cond = lock.newCondition();
    private int stage = 1;

    public void first(Runnable r) {
        lock.lock();
        try {
            System.out.print("first");
            stage++;
            cond.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void second(Runnable r) {
        lock.lock();
        try {
            while (stage != 2) cond.await();
            System.out.print("second");
            stage++;
            cond.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void third(Runnable r) {
        lock.lock();
        try {
            while (stage != 3) cond.await();
            System.out.print("third");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

Хотел бы отметить ещё один нюанс. В данной реализации для нотификации потоков используется метод signalAll(), который пробуждает все потоки находящиеся в состоянии wait. В выше описанном случае это не критично, но если потоков достаточно много, то это приведёт к избыточному пробуждению всех потоков, в то время как нужен только один. Эта проблема тоже решается разными способами, например созданием объекта Condition для каждого потока и усложнением логики нотификации с помощью метода signal().

→ Ссылка