java – ReentrantReadWriteLock多个读取线程

美好的一天

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

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

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

读取方法由许多不同的线程调用.从测量时间开始,即使从不调用写入方法,也会按顺序执行读取方法!关于这里出了什么问题的任何想法?提前谢谢-Cheers

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

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

我知道read-Lock中的部分实际上并不是读取而是写入.我这样做是因为有多个线程执行写操作,而只有一个线程执行读操作,但在读取时,不能对数组进行任何更改.这符合我的理解.再一次,只要没有添加写入方法和读取锁定,read-Locks中的代码就可以正常运行多个线程.

解决方法:

您的代码非常糟糕,您不必担心任何性能影响.您的代码不是线程安全的.永远不要在可变变量上同步!

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

此代码执行以下操作:

>在没有任何同步的情况下从位置读取数组帐户的内容,因此可能读取一个绝望的过时值,或者仅由仍在其同步块内的线程写入的值
>锁定它已读取的任何对象(请记住,未指定整数对象created by auto-boxing的标识[除-128到127范围外])
>再次读取位置的数组帐户的内容
>从其int值中减去金额,自动显示结果(在大多数情况下产生不同的对象)
>将新对象存储在位置的数组帐户中

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

它还意味着如果这些位置碰巧具有相同的值恰好由同一实例表示,则线程可能在不同的阵列位置上相互阻塞.例如.使用零值预先初始化数组(或者在-128到127范围内全部到相同的值)是接近单线程性能的好方法,因为零(或其他小值)是为数不多的整数值之一保证由同一个实例表示.由于您没有遇到NullPointerExceptions,显然您已经预先初始化了数组.

总而言之,synchronized同步适用于对象实例,而不是变量.这就是为什么在尝试对int变量进行编译时不会编译的原因.由于在不同对象上进行同步就像没有任何同步一样,所以永远不要在可变变量上进行同步.

如果您希望对不同帐户进行线程安全的并发访问,则可以使用AtomicIntegers.这样的解决方案将为每个帐户使用一个永远不会改变的AtomicInteger实例.仅使用其线程安全方法更新其余额值.

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

为了完整性,我想这个问题会出现,这里是一个撤销过程,禁止花费比现有更多的钱可能是这样的:

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

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

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

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;
}
上一篇:批量更新MySQL表


下一篇:C#-锁定成本