java - 尽管保护写操作仍获取竞争条件 - Java

标签 java multithreading concurrency synchronization

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

两个线程生成一个随机数,并用它来更新存储在类 Accounts 中的共享变量 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 接口(interface)如下:

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 条件,而另一个线程无限期地继续。我很困惑!

最佳答案

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

主题 1:

  1. 更新帐户(5);
  2. 获取锁定
  3. 账户1 += 5; -> 账户1 = 0 + 5; -> 账户1 = 5;

主题 2:

  1. getAccount1() <- 返回 5
  2. getAccount2() <- 返回 0
  3. 5+0 != 0 --> 退出

主题 1:

  1. 账户2 -= 5 -> 账户2 = 0-5 -> 账户2 = -5;
  2. 释放锁定。

解决方案: 不幸的是,您不能简单地单独同步您的 getter,我建议使用一种新的 get 方法:

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

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

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

关于java - 尽管保护写操作仍获取竞争条件 - Java,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7161153/

相关文章:

java - RandomAccessFile 中的结束行 StringBuilder

java - 如何让线程等待文件创建?

c++ - 带有定义参数的 Pthread

java - RxJava 而不是 AsyncTask?

concurrency - 单核 CPU 能否实现真正的并发?

java - 为什么 Kotlin 中没有并发关键字?

java - 使用 spring async 进行并行调用后连接结果

c# - 使用 WebSocket 在 Java 和 C# 桌面应用程序之间进行通信?

java - Java 中的异常翻译与异常链接

c# - 涉及异步调用时如何跟踪工作单元?