Получение состояния гонки, несмотря на защиту операций записи - Java - PullRequest
0 голосов
/ 23 августа 2011

Использование библиотеки java.util.concurrent.locks.ReentrantLock следующим образом:

Два потока генерируют случайное число и используют его для обновления общих переменных account1 и account2, хранящихся в классе Accounts - блокировка используется для защиты записи в общие переменные

package osproj221;
import java.util.concurrent.locks.ReentrantLock;

public class Accounts {
    private int account1,account2;
    private final ReentrantLock mutex;

    public Accounts(){
        account1=account2=0;
        mutex = new ReentrantLock();
    }

    public void updateAccounts(int amt){
        try{
            mutex.lock();
            account1 += amt;
            account2 -= amt;
        }catch(Exception ex){
            System.out.println(ex);
        }finally{mutex.unlock();}
    }

    public int getAccount1(){
        return this.account1;
    }

    public int getAccount2(){
        return this.account2;
    }
}

Мои потоки реализуют интерфейс Runnable следующим образом:

package osproj221;
import java.util.Random;

public class RaceThread implements Runnable {

    private Random myRand = new Random();
    private int counter = 0;
    private Accounts accounts;

    public RaceThread(Accounts accounts){
        this.accounts = accounts;
    }

    public void run(){

        do{
            int r = myRand.nextInt(300);
            r = Math.abs(r);
            accounts.updateAccounts(r);
            counter++;
        }while((accounts.getAccount1() + accounts.getAccount2() == 0));

        System.out.println(counter + " " + accounts.getAccount1() + " " + accounts.getAccount2());
    }

}

и наконец мой основной класс

package osproj221;

public class Main {

    public static void main(String[] args) {
        Accounts myAccounts = new Accounts();

        Thread t1 = new Thread(new RaceThread(myAccounts));
        Thread t2 = new Thread(new RaceThread(myAccounts));

        t1.start();
        t2.start();

        try{
            t1.join();
            t2.join();
        }catch(Exception ex){
            System.out.println(ex);
        }

        System.out.println(myAccounts.getAccount1() + " " + myAccounts.getAccount2());

    }

}

это выглядит немного дольше, чем я думал - извинения. Я ожидаю, что ни один из потоков не завершится, потому что account1 + account2 всегда должен = 0, поскольку мьютекс обрабатывает защиту обновления account1 и account2. Кажется, что происходит то, что один из потоков завершается, потому что он не выполняет условие account1 + account2 == 0, а другой продолжается бесконечно. я в замешательстве!

Ответы [ 4 ]

4 голосов
/ 23 августа 2011

Это потому, что вы не блокируете чтение на своих геттерах. Это означает, что поток 2 может читать данные в несогласованном состоянии, в то время как поток 1 обновляет данные (между + = и - =). Проблема может возникнуть следующим образом:

Тема 1:

  1. updateAccounts (5);
  2. ПОЛУЧИТЬ БЛОКИРОВКУ
  3. account1 + = 5; -> account1 = 0 + 5; -> account1 = 5;

Тема 2:

  1. getAccount1 () <- возвращает 5 </li>
  2. getAccount2 () <- возвращает 0 </li>
  3. 5 + 0! = 0 -> ВЫХОД

Тема 1:

  1. account2 - = 5 -> account2 = 0-5 -> account2 = -5;
  2. ОТКРЫТЬ ЗАМК.

Решение: К сожалению, вы не можете просто синхронизировать ваши геттеры по отдельности, я бы вместо этого рекомендовал новый метод get:

public int getAccountsSum() {
    try {
        mutex.lock();
        return this.account1 + this.account2;
    } finally { mutex.unlock(); }

и в RaceThread.run() изменить условие while на:

    }while((accounts.getAccountsSum() == 0));
1 голос
/ 23 августа 2011

вы только синхронизируете доступ для записи к переменным.

, поэтому существует вероятность того, что один поток установит новое значение account1, а другой читает старую account2.

1 голос
/ 23 августа 2011

Недостаточно синхронизировать ваши записи; вам также нужно синхронизировать чтение, используя тот же замок / монитор.

Если нет, могут случиться плохие вещи:

  • Нет гарантии, что запись в один поток будет когда-либо видимой для несинхронизированного считывателя в другом потоке.
  • Даже если записи видимы, нет гарантии, что они станут видимыми в ожидаемом порядке или что ваши две несинхронизированные операции чтения увидят согласованные значения для разных переменных. Вполне возможно, что вы увидите старое значение account1 и новое значение account2 или наоборот.
0 голосов
/ 23 августа 2011

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

  • Резьба1:
    • updateAccounts
      • ЗАМОК
      • account1 + = r
  • Резьба2:
    • getAccount1
    • getAccount2
    • account1 + account2! = 0
  • Резьба1:
      • account2 - = r
      • ОТКРЫВАЕТ

    public int getAccounts () { int result = 0; пытаться{ mutex.lock (); результат = account1 + account2; } catch (Exex ex) { System.out.println (ех); } Наконец {mutex.unlock ();} вернуть результат; }

...