首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ReentrantReadWriteLock多读线程

ReentrantReadWriteLock多读线程
EN

Stack Overflow用户
提问于 2015-04-10 15:17:21
回答 2查看 2.5K关注 0票数 4

好日子

我有一个关于ReentrantReadWriteLocks的问题。我试图解决这样一个问题:多个读取器线程应该能够在数据结构上并行操作,而一个写入线程只能单独操作(而没有一个读线程处于活动状态)。我正在用ReentrantReadWriteLocks在Java中实现这一点,但是从时间测量来看,读者线程似乎也将彼此锁定在外。我不认为这是应该发生的,所以我想知道我是否实现了错误。我实现它的方式如下:

代码语言:javascript
复制
readingMethod(){
    lock.readLock().lock();
    do reading ...
    lock.readLock().unlock();
}

writingMethod(){
    lock.writeLock().lock();
    do writing ...
    lock.writeLock().unlock();
}

其中读取方法由许多不同的线程调用。从测量时间开始,读取方法将按顺序执行,即使从未调用写入方法!你知道这里出了什么问题吗?谢谢你提前-Cheers

编辑:我试着想出一个SSCCE,我希望这是明确的:

代码语言:javascript
复制
public class Bank {
private Int[] accounts;
public ReadWriteLock lock = new ReentrantReadWriteLock();

// Multiple Threads are doing transactions.
public void transfer(int from, int to, int amount){
    lock.readLock().lock(); // Locking read.

    // Consider this the do-reading.
    synchronized(accounts[from]){
        accounts[from] -= amount;
    }
    synchronized(accounts[to]){
        accounts[to] += amount;
    }

    lock.readLock().unlock(); // Unlocking read.
}

// Only one thread does summation.
public int totalMoney(){
    lock.writeLock().lock; // Locking write.

    // Consider this the do-writing.
    int sum = 0;
    for(int i = 0; i < accounts.length; i++){
        synchronized(accounts[i]){
            sum += accounts[i];
        }
    }

    lock.writeLock().unlock; // Unlocking write.

    return sum;
}}

我知道读锁里面的部分实际上不是读而是写。我这样做是因为有多个线程执行写操作,而只有一个线程执行读取,但是在读取时,不能对数组进行任何更改。这在我的理解中是可行的。同样,只要不添加任何写方法和读锁,读锁内部的代码就能很好地工作在多个线程中。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-04-14 17:23:45

您的代码被严重破坏了,因此您不应该担心任何性能影响。您的代码不是线程安全的。永远不要在可变变量上同步!

代码语言:javascript
复制
synchronized(accounts[from]){
    accounts[from] -= amount;
}

此代码执行以下操作:

  • 在没有任何同步的情况下,在accounts位置读取数组from的内容,从而可能读取一个无可救药的过时值,或者只是由一个仍在其synchronized块中的线程编写的值。
  • 锁定它读取的任何对象(请记住,Integer对象的标识由自动装箱创建
  • 再次读取位于accounts位置的数组from的内容
  • 从其amount值中减去int值,将结果自动装箱(在大多数情况下产生一个不同的对象)
  • 将新对象存储在位于accounts位置的数组accounts

这意味着不同的线程可以同时写入相同的数组位置,同时在第一次(不同步)读取时对不同的Integer实例进行锁定,从而打开数据竞争的可能性。

这还意味着,如果这些位置的值恰好由同一个实例表示,那么线程可能会在不同的数组位置上彼此阻塞。例如,使用零值(或所有值都在-128到+127范围内)对数组进行预初始化是接近单个线程性能的好方法,因为零(或其他小值)是保证由同一个实例表示的为数不多的Integer值之一。由于您没有体验到NullPointerException,所以很明显,您已经用某种东西对数组进行了预初始化。

总之,synchronized处理的是对象实例,而不是变量。这就是为什么它在尝试在int变量上进行编译时不会编译的原因。由于对不同对象的同步就像根本没有任何同步一样,所以不应该对可变变量进行同步。

如果您想要线程安全的并发访问不同的帐户,您可以使用AtomicInteger。这样的解决方案将使用一个AtomicInteger实例的每个帐户,这将永远不会改变。只有它的余额值将使用其线程安全方法进行更新。

代码语言:javascript
复制
public class Bank {
    private final AtomicInteger[] accounts;
    public final ReadWriteLock lock = new ReentrantReadWriteLock();
    Bank(int numAccounts) {
        // initialize, keep in mind that this array MUST NOT change
        accounts=new AtomicInteger[numAccounts];
        for(int i=0; i<numAccounts; i++) accounts[i]=new AtomicInteger();
    }

    // Multiple Threads are doing transactions.
    public void transfer(int from, int to, int amount){
        final Lock sharedLock = lock.readLock();
        sharedLock.lock();
        try {
            accounts[from].addAndGet(-amount);
            accounts[to  ].addAndGet(+amount);
        }
        finally {
            sharedLock.unlock();
        }
    }

    // Only one thread does summation.
    public int totalMoney(){
        int sum = 0;
        final Lock exclusiveLock = lock.writeLock();
        exclusiveLock.lock();
        try {
            for(AtomicInteger account: accounts)
                sum += account.get();
        }
        finally {
            exclusiveLock.unlock();
        }
        return sum;
    }
}

为了完整起见,正如我猜会出现的问题,以下是一个禁止收取比可用资金更多的资金的退出过程是怎样的:

代码语言:javascript
复制
static void safeWithdraw(AtomicInteger account, int amount) {
    for(;;) {
        int current=account.get();
        if(amount>current) throw new IllegalStateException();
        if(account.compareAndSet(current, current-amount)) return;
    }
}

它可以通过将行accounts[from].addAndGet(-amount);替换为safeWithdraw(accounts[from], amount);来包含。

在写完上面的例子之后,我记得有一个类AtomicIntegerArray更适合这种任务…。

代码语言:javascript
复制
private final AtomicIntegerArray accounts;
public final ReadWriteLock lock = new ReentrantReadWriteLock();

Bank(int numAccounts) {
    accounts=new AtomicIntegerArray(numAccounts);
}

// Multiple Threads are doing transactions.
public void transfer(int from, int to, int amount){
    final Lock sharedLock = lock.readLock();
    sharedLock.lock();
    try {
        accounts.addAndGet(from, -amount);
        accounts.addAndGet(to,   +amount);
    }
    finally {
        sharedLock.unlock();
    }
}

// Only one thread does summation.
public int totalMoney(){
    int sum = 0;
    final Lock exclusiveLock = lock.writeLock();
    exclusiveLock.lock();
    try {
        for(int ix=0, num=accounts.length(); ix<num; ix++)
            sum += accounts.get(ix);
    }
    finally {
        exclusiveLock.unlock();
    }
    return sum;
}
票数 4
EN

Stack Overflow用户

发布于 2015-04-10 15:36:21

您可以在此测试上运行两个线程。

代码语言:javascript
复制
static ReadWriteLock l = new ReentrantReadWriteLock();

static void readMehod() {
    l.readLock().lock();
    System.out.println(Thread.currentThread() + " entered");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    l.readLock().unlock();
    System.out.println(Thread.currentThread() + " exited");
}

看看两个线程是否都进入了重新锁。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29565031

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档