尽管保护写操作,但仍然获得竞争条件 - Java

时间:2011-08-23 12:40:32

标签: java multithreading concurrency synchronization

使用java.util.concurrent.locks.ReentrantLock库,如下所示:

两个线程生成一个随机数,并使用它来更新存储在类Account中的共享变量account1和account2 - 一个锁用于保护写入共享变量

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的更新时,account1 + account2应该总是= 0。似乎发生的是其中一个线程退出,因为它失败了account1 + account2 == 0条件,而另一个线程无限期地继续。我很困惑!

4 个答案:

答案 0 :(得分:4)

这是因为你没有锁定吸气剂上的读数。这意味着线程2可以读取处于不一致状态的数据,而线程1正在更新数据(在+ =和 - =之间)。 问题可能发生如下:

主题1:

  1. updateAccounts(5);
  2. 获得锁定
  3. account1 + = 5; - > account1 = 0 + 5; - > account1 = 5;
  4. 主题2:

    1. getAccount1()< - return 5
    2. getAccount2()< - return 0
    3. 5 + 0!= 0 - > EXIT
    4. 主题1:

      1. account2 - = 5 - > account2 = 0-5 - > account2 = -5;
      2. 释放锁定。
      3. 解决方案: 不幸的是,你不能单独同步你的getter我会改为推荐一个新的get方法:

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

        并在RaceThread.run()中将while条件更改为:

            }while((accounts.getAccountsSum() == 0));
        

答案 1 :(得分:1)

同步你的写作是不够的;您还需要使用相同的锁定/监视器同步您的读取。

如果不这样做,可能会发生不好的事情:

  • 无法保证一个线程上的写入永远对另一个线程上的非同步读者可见
  • 即使写入是可见的,也不能保证它们将以预期的顺序变为可见,或者您的两个未同步的读取将看到不同变量的相干值。您很有可能会看到旧值account1和新值account2,反之亦然。

答案 2 :(得分:1)

您只是在同步对变量的写访问权。

因此,有一个线程将帐户1设置为新值而另一个线程正在读取旧帐户2。

答案 3 :(得分:0)

你的Getters没有被锁定。因此,即使您使用互斥锁进行更新,其他线程也会调用可以解锁的Getters。

  • 线程1:
    • updateAccounts
      • LOCK
      • account1 + = r
  • 线程2:
    • getAccount1
    • getAccount2
    • account1 + account2!= 0
  • 线程1:
      • account2 - = r
      • UNLOCK

    public int getAccounts(){     int result = 0;     尝试{         mutex.lock();         result = account1 + account2;     } catch(Exception ex){         的System.out.println(前);     最后} {mutex.unlock();}     返回结果; }