Java - разрешить одному потоку обновлять значение, другим ждать и пропускать критический раздел - PullRequest
0 голосов
/ 30 сентября 2019

Привет, у меня есть ситуация, в которой я должен разрешить только одному потоку сказать обновить переменную.

Существует триггер, который может вызывать несколько потоков для обновления этой переменной, однако обновление должно происходить только один раз первым потоком, который достигнет критической секции.

В идеале поток должен быть следующим:

Thread-1;Thread-2 и Thread-3 вызываются для обновления переменной в критической секции, защищенной блокировкой или мьютексом

Критическая секция с использованием этой защиты позволяет входить только одному потоку, Thread-2 и Thread-3 просто ждутснаружи.

Как только эта переменная обновлена ​​Thread-1;Thread-2 и Thread-3 возобновляют работу, не влияя на переменную.

Я предложил следующую реализацию, но я не могу заставить другие потоки ждать и пропустить обновление:

public class Main {


    private static ReentrantLock lock = new ReentrantLock();
    private int counter = 0;

    public static void main(String[] args) {
        Main m = new Main();

        new Thread(m::doSomeOperation).start();

        new Thread(m::doSomeOperation).start();

        new Thread(m::doSomeOperation).start();


    }


    private void doSomeOperation() {

    try {
        System.out.println("Thread about to acquire lock: " + Thread.currentThread().getName());
        if (lock.tryLock()) {
            System.out.println("Lock held by " + Thread.currentThread().getName() + " " + lock.isHeldByCurrentThread());
            counter++;
            // Thread.sleep(3000);
            System.out.println("Counter value: " + counter + " worked by thread " + Thread.currentThread().getName());
        }

    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
            System.out.println("Unlocked: " + Thread.currentThread().getName());
        }
    }


}
}

В конце значение счетчика равно 3, я хочузначение счетчика равно единице, и я хочу, чтобы другие потоки подождали, пока первый поток обновит счетчик. Идеи приветствуются. Я бы предпочел решение, использующее блокировки / мьютекс, а не ждать и уведомлять.

Вывод:

Thread about to acquire lock: Thread-0
Lock held by Thread-0 true
Thread about to acquire lock: Thread-1
Counter value: 1 worked by thread Thread-0
Unlocked: Thread-0
Thread about to acquire lock: Thread-2
Lock held by Thread-2 true
Counter value: 2 worked by thread Thread-2
Unlocked: Thread-2

Process finished with exit code 0

Примечание Мой вариант использования отличается - пример обновления счетчика приведен для простоты. На самом деле я обновляю токен сеанса в методе doSomeOperation.

Ответы [ 4 ]

1 голос
/ 30 сентября 2019

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

Здесь один поток, получивший блокировку, уменьшает число защелок на единицу и обнуляет его, после чего ни один из потоков не сможет обработать, потому что условие latch.getCount()==1 не будет выполнено.

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;

public class Test2 {


    private static ReentrantLock lock = new ReentrantLock();
    private static CountDownLatch latch= new CountDownLatch(1);
    private int counter = 0;

    public static void main(String[] args) {
        Test2 m = new Test2();

        new Thread(m::doSomeOperation).start();

        new Thread(m::doSomeOperation).start();

        new Thread(m::doSomeOperation).start();


    }


    private void doSomeOperation() {

    try {
        System.out.println("Thread about to acquire lock: " + Thread.currentThread().getName());
        if (lock.tryLock() && latch.getCount()==1) {
            System.out.println("Lock held by " + Thread.currentThread().getName() + " " + lock.isHeldByCurrentThread());
            counter++;
            latch.countDown();
             Thread.sleep(3000);
            System.out.println("Counter value: " + counter + " worked by thread " + Thread.currentThread().getName());
            }
        System.out.println("Exiting" + Thread.currentThread().getName());

    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
            System.out.println("Unlocked: " + Thread.currentThread().getName());
        }
    }


}
}
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;

public class Test2 {


    private static ReentrantLock lock = new ReentrantLock();
    private static CountDownLatch latch= new CountDownLatch(1);
    private int counter = 0;

    public static void main(String[] args) {
        Test2 m = new Test2();

        new Thread(m::doSomeOperation).start();

        new Thread(m::doSomeOperation).start();

        new Thread(m::doSomeOperation).start();


    }


    private void doSomeOperation() {

    try {
        System.out.println("Thread about to acquire lock: " + Thread.currentThread().getName());
        if (lock.tryLock() && latch.getCount()==1) {
            System.out.println("Lock held by " + Thread.currentThread().getName() + " " + lock.isHeldByCurrentThread());
            counter++;
            latch.countDown();
             Thread.sleep(3000);
            System.out.println("Counter value: " + counter + " worked by thread " + Thread.currentThread().getName());
            }
        System.out.println("Exiting" + Thread.currentThread().getName());

    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
            System.out.println("Unlocked: " + Thread.currentThread().getName());
        }
    }


}
}
1 голос
/ 30 сентября 2019

Похоже, то, что вы пытаетесь построить, является классическим примером проблемы Producer Consumer. Вы найдете тысячи решений онлайн для этой проблемы. некоторые из них перечислены ниже.

0 голосов
/ 30 сентября 2019

Если вам известно точное количество потоков, которые вы начинаете в каждом раунде: вы можете сделать это следующим образом:

 private int threadCounter;
 private int threadCount = 3;
    private void doSomeOperation() {

        try {
            System.out.println("Thread about to acquire lock: " + Thread.currentThread().getName());
            if (lock.tryLock()) {
                System.out.println("Lock held by " + Thread.currentThread().getName() + " " + lock.isHeldByCurrentThread());
                if (threadCounter++ % threadCount == 0) {
                    counter++;
                    // Thread.sleep(3000);
                    System.out.println("Counter value: " + counter + " worked by thread " + Thread.currentThread().getName());
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                System.out.println("Unlocked: " + Thread.currentThread().getName());
            }
        }

    }

Это позволит увеличить счетчик только до первого потока в каждом раунде.

0 голосов
/ 30 сентября 2019

Вы можете использовать AtomicInteger здесь и отказаться от беспокойства о формальных блокировках:

public class Worker {
    private static AtomicInteger counter = new AtomicInteger(0);

    private void doSomeOperation() {
        counter.incrementAndGet();
        System.out.println("Counter value: " + counter + " worked by thread " + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        Worker w = new Worker();
        new Thread(w::doSomeOperation).start();
        new Thread(w::doSomeOperation).start();
        new Thread(w::doSomeOperation).start();
    }
}
...