简体   繁体   English

java中的并发读/写缓冲区

[英]Concurrent read/write buffer in java

I am trying to implement a read/write buffer class where it can be able to support multiple writers and readers, and the reader can read the buffer simultaneously while the writer is writing the buffer. 我正在尝试实现一个读/写缓冲区类,它可以支持多个写入器和读取器,并且读取器可以在写入缓冲区时同时读取缓冲区。 Here's my code, and so far I haven't seen any issue, but I am not 100% sure if this is thread-safe or if there's any better approach. 这是我的代码,到目前为止我还没有看到任何问题,但我不能100%确定这是否是线程安全的,或者是否有更好的方法。

public class Buffer{
       private StringBuilder sb = new StringBuilder();
       private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
       private Random random = new Random();

       public void read(){
            try{
                lock.readLock().lock();
                System.out.println(sb.toString());
            } finally{
                lock.readLock().unlock();
            }
       }
       public void write(){
            try{
                lock.writeLock().lock();
                sb.append((char)(random.nextInt(26)+'a'));
            } finally{
                lock.writeLock().unlock();
            }
       }
} 

No problem with multi-threading safety whatsoever! 多线程安全无任何问题! The read and write locks protect access to the StringBuilder and the code is clean and easy to read. 读写锁可以保护对StringBuilder的访问,代码简洁易读。

By using ReentrantReadWriteLock you are actually maximising you chance of achieving higher degrees of concurrency, because multiple readers can proceed together, so this is a better solution than using plain old synchronised methods. 通过使用ReentrantReadWriteLock,您实际上可以最大限度地提高实现更高并发度的机会,因为多个读者可以一起进行,因此这比使用普通的旧同步方法更好。 However, contrary to what is stated in the question, the code does not allow a writer to write while the readers are reading. 然而,相反的是在问题说明,代码不允许一个作家,而读者阅读写作。 This is not necessarily a problem in itself though. 但这本身并不一定是个问题。

The readers acquire a read lock before proceeding. 读者在继续之前获得了读锁定。 The writers acquire a write lock before proceeding. 在继续之前,编写器获取写锁定。 The rules of read locks allow one to be acquired when there is no write lock (but it is OK if there are some read locks ie if there are more active readers). 读锁的规则允许在没有写锁时获取一个(但是如果有一些读锁,即如果有更多活动读取器则可以)。 The rules of write locks allow one to be acquired if and only if there are no other locks (no readers, no writers). 当且仅当没有其他锁(没有读者,没有编写者)时,写锁的规则允许获取一个。 Thus multiple readers are allowed but only a single writer. 因此允许多个读者,但只允许一个作者。

The only change that might be needed would be to change the lock initialisation code to this: 可能需要的唯一更改是将锁定初始化代码更改为:

private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

As is the original code given in the question does not require the lock to be fair. 由于问题中给出的原始代码不要求锁是公平的。 With the above change it is guaranteed that "threads contend for entry using an approximately arrival-order policy. When the write lock is released either the longest-waiting single writer will be assigned the write lock, or if there is a reader waiting longer than any writer, the set of readers will be assigned the read lock. When constructed as non-fair, the order of entry to the lock need not be in arrival order." 通过上述更改,可以保证“线程使用近似到达顺序策略争用入口。当释放写入锁定时,最长等待的单个写入器将被分配写入锁定,或者如果读取器等待的时间长于任何作家,读者都将获得读锁定。当构造为非公平时,进入锁定的顺序不必是到达顺序。“ (Taken from http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html ) (摘自http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html

See also the following (from the same source): 另请参阅以下内容(来自同一来源):

ReentrantReadWriteLocks can be used to improve concurrency in some uses of some kinds of Collections. ReentrantReadWriteLocks可用于在某些类型的集合的某些用途中提高并发性。 This is typically worthwhile only when the collections are expected to be large, accessed by more reader threads than writer threads, and entail operations with overhead that outweighs synchronization overhead. 这通常是值得的,只有当预期集合很大时,由更多的读取器线程访问而不是编写器线程,并且需要具有超过同步开销的开销的操作。 For example, here is a class using a TreeMap that is expected to be large and concurrently accessed. 例如,这是一个使用TreeMap的类,该类预计很大并且可以同时访问。

class RWDictionary {
    private final Map<String, Data>  m = new TreeMap<String, Data>();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Data get(String key) {
        r.lock(); try { return m.get(key); } finally { r.unlock(); }
    }
    public String[] allKeys() {
       r.lock(); try { return m.keySet().toArray(); } finally { r.unlock(); }
    }
    public Data put(String key, Data value) {
        w.lock(); try { return m.put(key, value); } finally { w.unlock(); }
    }
    public void clear() {
        w.lock(); try { m.clear(); } finally { w.unlock(); }
   }
 }

The excerpt from the API documentation is particularly performance conscious. API文档的摘录特别注重性能。 In your specific case, I cannot comment on whether you meet the "large collection" criterion, but I can say that outputting to the console is much more time consuming than the thread-safety mechanism overhead. 在您的具体情况下,我无法评论您是否符合“大集合”标准,但我可以说输出到控制台比线程安全机制开销更耗时。 At any rate, you use of ReentrantReadWriteLocks makes perfect sense from a logical point of view and is perfectly thread safe. 无论如何,从逻辑的角度来看,使用ReentrantReadWriteLocks是完全合理的,并且完全是线程安全的。 This is nice code to read :-) 这是很好的代码:-)

Note 1 (answering question about exceptions found in the comments of the original question): Taken from http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/Lock.html lock() acquires the lock. 注1(回答原问题评论中的异常问题):取自http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/Lock.html lock()获取锁。 If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired. 如果锁定不可用,则当前线程将被禁用以进行线程调度,并且在获取锁定之前处于休眠状态。

A Lock implementation may be able to detect erroneous use of the lock, such as an invocation that would cause deadlock, and may throw an (unchecked) exception in such circumstances. Lock实现可能能够检测到锁的错误使用,例如可能导致死锁的调用,并且可能在这种情况下抛出(未经检查的)异常。 The circumstances and the exception type must be documented by that Lock implementation. 必须通过Lock实现记录环境和异常类型。

No indication of such exceptions is given in the relevant documentation for ReentrantReadWriteLock.ReadLock ( http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.ReadLock.html ) or ReentrantReadWriteLock.WriteLock ( http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.WriteLock.html ) ReentrantReadWriteLock.ReadLock的相关文档中没有给出此类异常的指示( http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.ReadLock.html )或ReentrantReadWriteLock.WriteLock( http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.WriteLock.html

Note 2: While access to the StringBuilder is protected by the locks, System.out is not. 注意2:虽然对StringBuilder的访问受锁的保护,但System.out却没有。 In particular, multiple readers may read the value concurrently and try to output it concurrently. 特别是,多个读取器可以同时读取该值并尝试同时输出它。 That is also OK, because access to System.out.println() is synchronized. 这也没关系,因为对System.out.println()的访问是同步的。

Note 3: If you want to disallow multiple active writers, but allow a writer and one or more readers to be active at the same time, you can simple skip using read locks altogether ie delete lock.readLock().lock(); 注3:如果你想禁止多个活动的编写器,但允许一个编写器和一个或多个读者同时处于活动状态,你可以简单地跳过使用读锁,即删除lock.readLock()。lock(); and lock.readLock().unlock(); 和lock.readLock()。unlock(); in your code. 在你的代码中。 However, in this particular case this would be wrong. 但是,在这种特殊情况下,这是错误的。 You need to stop concurrent reading and writing to the StringBuilder. 您需要停止并发读取和写入StringBuilder。

The description and the code seem to be two different things. 描述和代码似乎是两个不同的东西。 You say in the description that you want to let the readers read while the writer (one at a time I assume) writes. 你在描述中说,你想让读者在作者(我一次假设一个人)写的时候读。 However you have a lock in your read method too. 但是,您也可以锁定读取方法。 So right now you have one, reader or writer at a time accessing your buffer. 所以现在你有一个,读者或作者一次访问你的缓冲区。

If you want to have readers access while there is a writer, remove the lock from the read method. 如果您希望在有编写器时让读者访问,请从read方法中删除锁定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM