![](/img/trans.png)
[英]what happens when multiple threads want to access a ReentrantReadWriteLock?
[英]ReentrantReadWriteLock multiple reading threads
美好的一天
我有一个关于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;
}
此代码执行以下操作:
from
没有任何同步的位置读取数组accounts
的内容,从而可能读取一个绝望的过时值,或者只是由仍在其synchronized
块内的线程写入的值 Integer
对象的标识未指定[除了-128到+127范围]) accounts
在位置from
int
值中减去amount
,自动显示结果(在大多数情况下产生不同的对象) accounts
在位置from
这意味着不同的线程可以同时写入同一个数组位置,同时锁定在第一个(非同步)读取时发现的不同Integer
实例,从而打开了数据争用的可能性。
它还意味着如果这些位置碰巧具有相同的值恰好由同一实例表示,则线程可能在不同的阵列位置上相互阻塞。 例如,使用零值预先初始化数组(或者在-128到+127范围内的所有相同值)是接近单线程性能的良好方法,因为零(或其他小值)是为数不多的之一保证Integer
数值由同一实例表示。 由于您没有遇到NullPointerException
,您显然已经预先初始化了数组。
总而言之, synchronized
适用于对象实例,而不是变量。 这就是为什么在尝试对int
变量进行编译时不会编译的原因。 由于在不同对象上进行同步就像没有任何同步一样,所以永远不要在可变变量上进行同步。
如果您希望对不同帐户进行线程安全的并发访问,则可以使用AtomicInteger
。 这样的解决方案将为每个帐户使用一个永远不会改变的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;
}
}
可以通过替换行accounts[from].addAndGet(-amount);
来包含它accounts[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;
}
您可以在此测试中运行2个线程
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");
}
并查看两个线程是否都进入了readlock。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.