ReentrantReadWriteLock多个读取线程 [英] ReentrantReadWriteLock multiple reading threads

查看:64
本文介绍了ReentrantReadWriteLock多个读取线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

美好的一天

我有一个与ReentrantReadWriteLocks有关的问题.我正在尝试解决一个问题,其中多个读取器线程应该能够在一个数据结构上并行运行,而一个写入器线程只能单独运行(而没有任何读取器线程处于活动状态).我正在用Java中的ReentrantReadWriteLocks实现这一点,但是从时间的角度来看,阅读器线程似乎也相互锁定了.我不认为这应该发生,所以我想知道我是否实施错误.我的实现方式如下:

I have a question relating ReentrantReadWriteLocks. I am trying to solve a problem where multiple reader threads should be able to operate in parallel on a data structure, while one writer thread can only operate alone (while no reader thread is active). I am implementing this with the ReentrantReadWriteLocks in Java, however from time measurement it seems that the reader threads are locking each other out aswell. I don't think this is supposed to happen, so I am wondering if I implemented it wrong. The way I implemented it is as follows:

readingMethod(){
    lock.readLock().lock();
    do reading ...
    lock.readLock().unlock();
}

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

许多不同的线程都在调用read方法.从测量时间开始,即使从未调用过写入方法,也将按顺序执行读取方法!对这里出什么问题有任何想法吗?预先谢谢你-干杯

Where the reading method is called by many different threads. From measuring the time, the reading method is being executed sequentially, even if the writing method is never invoked! Any Idea on what is going wrong here? Thank you in advance -Cheers

我试图提出一个SSCCE,我希望这很清楚:

I tried to come up with a SSCCE, I hope this is clear:

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;
}}

我知道读锁内部的部分实际上不是读取而是写入.我这样做是因为有多个线程执行写操作,而只有一个线程执行读操作,但是在读取时,无法对数组进行任何更改.以我的理解,这是可行的.同样,只要没有写方法和读锁都没有,读锁内部的代码就可以在多个线程上正常工作.

I know the parts inside the read-Lock are not actually reads but writes. I did it this way because there are multiple threads performing writes, while only one thread performs reads, but while reading, no changes can be made to the array. This works in my understanding. And again, the code inside the read-Locks works fine with multiple threads, as long as no write method and no read-locks are added.

推荐答案

您的代码是如此糟糕,以至于您不必担心任何性能隐患.您的代码不是线程安全的. 永远不要在可变变量上同步

Your code is so horribly broken that you should not worry about any performance implication. Your code is not thread safe. Never synchronize on a mutable variable!

synchronized(accounts[from]){
    accounts[from] -= amount;
}

此代码执行以下操作:

  • 读取位置from处的数组accounts的内容时不进行任何同步,因此可能会读取无望的过时的值,或者仅由仍在其synchronized块内的线程写入的值
  • 锁定已读取的任何对象(请记住,Integer对象的标识
  • read the contents of the array accounts at position from without any synchronization, thus possibly reading a hopelessly outdated value, or a value just being written by a thread still inside its synchronized block
  • lock on whatever object it has read (keep in mind that the identity of Integer objects created by auto-boxing is unspecified [except for the -128 to +127 range])
  • read again the contents of the array accounts at position from
  • subtract amount from its int value, auto-box the result (yielding a different object in most cases)
  • store the new object in array accounts at position from

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

This implies that different threads can write to the same array position concurrently while having a lock on different Integer instances found on their first (unsynchronized) read, opening the possibility of data races.

这还意味着,如果这些位置碰巧具有由相同实例表示的相同值,则它们可能在不同的数组位置上相互阻塞.例如.使用零值(或所有值都在-128到+127范围内的所有相同值)预初始化数组是接近单线程性能的好方法,因为零(或这些其他小值)是少数几个Integer值保证由同一实例表示.由于您没有经历过NullPointerException,因此显然您已经使用一些东西对数组进行了预初始化.

It also implies that threads may block each other on different array positions if these positions happen to have the same value happened to be represented by the same instance. E.g. pre-initializing the array with zero values (or all to the same value within the range -128 to +127) is a good recipe for getting close to single thread performance as zero (or these other small values) is one of the few Integer values being guaranteed to be represented by the same instance. Since you didn’t experience NullPointerExceptions, you obviously have pre-initialized the array with something.

总而言之,synchronized适用于对象实例,而不适用于变量.这就是为什么在int变量上尝试编译时无法编译的原因.由于在不同对象上进行同步就像根本不进行任何同步一样,因此您永远不应在可变变量上进行同步.

To summarize, synchronized works on object instances, not variables. That’s why it won’t compile when trying to do it on int variables. Since synchronizing on different objects is like not having any synchronization at all, you should never synchronize on mutable variables.

如果您想要线程安全的并发访问不同帐户,可以使用

If you want thread-safe, concurrent access to the different accounts, you may use AtomicIntegers. Such a solution will use exactly one AtomicInteger instance per account which will never change. Only its balance value will be updated using its thread-safe methods.

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;
    }
}

出于完整性考虑,我猜这个问题将会出现,这是一个禁止取款超过可用资金的取款过程的样子:

For completeness, as I guess this question will arise, here is how a withdraw process forbidding taking more money than available may look like:

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;
    }
}

可以用safeWithdraw(accounts[from], amount);代替accounts[from].addAndGet(-amount);行.

在编写完上面的示例之后,我记得有一个类

Well after writing the example above, I remembered that there is the class AtomicIntegerArray which fits even better to this kind of task…

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;
}

这篇关于ReentrantReadWriteLock多个读取线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆