Kug*_*itz 4 java concurrency multithreading locking thread-safety
再会
我有一个有关 ReentrantReadWriteLocks 的问题。我正在尝试解决一个问题,其中多个读取器线程应该能够在数据结构上并行操作,而一个写入器线程只能单独操作(同时没有读取器线程处于活动状态)。我正在使用 Java 中的 ReentrantReadWriteLocks 来实现这一点,但是从时间测量来看,读取器线程似乎也相互锁定。我认为这不应该发生,所以我想知道我是否实施错误。我的实现方式如下:
readingMethod(){
lock.readLock().lock();
do reading ...
lock.readLock().unlock();
}
writingMethod(){
lock.writeLock().lock();
do writing ...
lock.writeLock().unlock();
}
Run Code Online (Sandbox Code Playgroud)
其中读取方法被许多不同的线程调用。从测量时间来看,即使从未调用写入方法,读取方法也会按顺序执行!关于这里出了什么问题有什么想法吗?提前谢谢你-干杯
编辑:我试图提出一个 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;
}}
Run Code Online (Sandbox Code Playgroud)
我知道读锁里面的部分实际上不是读而是写。我这样做是因为有多个线程执行写入,而只有一个线程执行读取,但是在读取时,不能对数组进行任何更改。这在我的理解中是有效的。同样,只要不添加写入方法和读取锁,读取锁内的代码就可以在多个线程中正常工作。
您的代码已经严重损坏,您不必担心任何性能影响。您的代码不是线程安全的。切勿在可变变量上同步!
\n\nsynchronized(accounts[from]){\n accounts[from] -= amount;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n该代码执行以下操作:
\n\naccounts在没有任何同步的情况下读取该位置的数组内容from,因此可能读取一个无可救药的过时值,或者一个仍在其synchronized块内的线程正在写入的值Integer对象的标识是未指定的[-128到+127范围除外])accounts再次读取数组中位置的内容fromamount从其值中减去int,自动装箱结果(在大多数情况下产生不同的对象)accounts中的位置from这意味着不同的线程可以同时写入相同的数组位置,同时锁定Integer在其第一次(不同步)读取时发现的不同实例,从而打开了数据竞争的可能性。
它还意味着,如果这些位置碰巧具有由同一实例表示的相同值,则线程可能会在不同的数组位置上相互阻塞。例如,用零值(或全部为 -128 到 +127 范围内的相同值)预初始化数组是接近单线程性能的好方法,因为零(或这些其他小值)是少数几个之一Integer保证值由同一个实例表示。由于您没有\xe2\x80\x99t 经历过NullPointerExceptions,因此您显然已经用某些东西预先初始化了数组。
总而言之,synchronized适用于对象实例,而不是变量。这就是为什么在尝试对变量执行此操作时无法编译 xe2x80x99 的原因int。由于在不同对象上同步就像根本没有任何同步一样,因此您永远不应该在可变变量上同步。
如果您想要对不同帐户进行线程安全的并发访问,您可以使用AtomicIntegers. 这样的解决方案将为AtomicInteger每个帐户仅使用一个实例,并且永远不会改变。只有其余额值才会使用其线程安全方法进行更新。
public class Bank {\n private final AtomicInteger[] accounts;\n public final ReadWriteLock lock = new ReentrantReadWriteLock();\n Bank(int numAccounts) {\n // initialize, keep in mind that this array MUST NOT change\n accounts=new AtomicInteger[numAccounts];\n for(int i=0; i<numAccounts; i++) accounts[i]=new AtomicInteger();\n }\n\n // Multiple Threads are doing transactions.\n public void transfer(int from, int to, int amount){\n final Lock sharedLock = lock.readLock();\n sharedLock.lock();\n try {\n accounts[from].addAndGet(-amount);\n accounts[to ].addAndGet(+amount);\n }\n finally {\n sharedLock.unlock();\n }\n }\n\n // Only one thread does summation.\n public int totalMoney(){\n int sum = 0;\n final Lock exclusiveLock = lock.writeLock();\n exclusiveLock.lock();\n try {\n for(AtomicInteger account: accounts)\n sum += account.get();\n }\n finally {\n exclusiveLock.unlock();\n }\n return sum;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n为了完整起见,我猜会出现这个问题,以下是禁止提取超出可用资金的提款流程:
\n\nstatic void safeWithdraw(AtomicInteger account, int amount) {\n for(;;) {\n int current=account.get();\n if(amount>current) throw new IllegalStateException();\n if(account.compareAndSet(current, current-amount)) return;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\naccounts[from].addAndGet(-amount);可以通过将行替换为 来包含它safeWithdraw(accounts[from], amount);。
写完上面的例子后,我记得有一个类AtomicIntegerArray更适合这种任务\xe2\x80\xa6
private final AtomicIntegerArray accounts;\npublic final ReadWriteLock lock = new ReentrantReadWriteLock();\n\nBank(int numAccounts) {\n accounts=new AtomicIntegerArray(numAccounts);\n}\n\n// Multiple Threads are doing transactions.\npublic void transfer(int from, int to, int amount){\n final Lock sharedLock = lock.readLock();\n sharedLock.lock();\n try {\n accounts.addAndGet(from, -amount);\n accounts.addAndGet(to, +amount);\n }\n finally {\n sharedLock.unlock();\n }\n}\n\n// Only one thread does summation.\npublic int totalMoney(){\n int sum = 0;\n final Lock exclusiveLock = lock.writeLock();\n exclusiveLock.lock();\n try {\n for(int ix=0, num=accounts.length(); ix<num; ix++)\n sum += accounts.get(ix);\n }\n finally {\n exclusiveLock.unlock();\n }\n return sum;\n}\nRun Code Online (Sandbox Code Playgroud)\n