Использование семафоров java в качестве блокировки между двумя классами Runnable - PullRequest
1 голос
/ 01 мая 2020

У меня есть три объекта, которые являются экземплярами двух разных классов, реализующих интерфейс Runnable. Один из объектов изменяет счетчики двух других объектов, но я хочу убедиться, что вся операция обновления не прерывается другими потоками (т. Е. Я хочу использовать блокировку для своего критического раздела).

В код ниже (это иллюстрация реального кода, а не самого), я хочу убедиться, что код в критическом разделе выполняется без каких-либо перерывов.

Одна мысль, которая у меня возникла, - определить двоичный семафор m в классе Worker и окружить каждую операцию, которая касается value и operations, m.acquire(), за которой следует m.release(). Но в классе 'Runner' у меня есть вызов на incrementValue(), и если я окружаю CS вызовами acquire()/release(), в то время как у меня то же самое в incrementValue(), это не имеет смысла.

Я немного озадачен тем, куда я должен поместить свои семафоры для достижения взаимного исключения.

Спасибо

class Worker implements Runnable{
    int value;
    int operations;
    // Semaphore m = new Semaphore(1);
    ...
    ...
    void incrementValue(int n){
        // m.acquire() here??
        this.operations++;
        this.value += n;
        // m.release() here??
    }
    ...
    @Override
    public void run(){
        ...
        this.operations++;
        this.value = getRandomNum(); 
        ...
    }
}

class Runner implements Runnable {
    Worker a, b;
    ...
    ...
    @Override
    public void run(){
        ...
        // Start of the CS
        // a.m.acquire() here?
        // b.m.acquire() here?
        a.incrementValue(x);
        System.out.println("Value in WorkerA incremented by " + x);
        b.incrementValue(y);
        System.out.println("Value in WorkerB incremented by " + y);
        // a.m.release() here?
        // b.m.release() here?
        // end of the CS
        ...
    }
    ...
}

1 Ответ

1 голос
/ 01 мая 2020

Похоже, проблема, с которой вы столкнулись, - это та же проблема, которую ReentrantLock предназначена для ее решения. ReentrantLock позволяет вам сделать это:

final ReentrantLock m = new ReentrantLock();

void foo() {
    m.lock();
    doFooStuff();
    m.unlock();
}

void bar() {
    m.lock();
    foo();
    doAdditionalBarStuff();
    m.unlock();
}

Вызов lock() проверяет, владеет ли вызывающий поток блокировкой. Если вызывающая сторона этого не делает, то сначала она получает блокировку, ждет, если необходимо, и, наконец, перед тем, как вернуть, устанавливает переменную count в 1.

Последующие lock() вызовы из того же потока будут видеть что поток уже владеет блокировкой, и они просто увеличивают счетчик и возвращают.

Вызов unlock() уменьшает счетчик и снимает блокировку только тогда, когда счетчик достигает нуля.

...