繁体   English   中英

java中的并发读/写缓冲区

[英]Concurrent read/write buffer in java

我正在尝试实现一个读/写缓冲区类,它可以支持多个写入器和读取器,并且读取器可以在写入缓冲区时同时读取缓冲区。 这是我的代码,到目前为止我还没有看到任何问题,但我不能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();
            }
       }
} 

多线程安全无任何问题! 读写锁可以保护对StringBuilder的访问,代码简洁易读。

通过使用ReentrantReadWriteLock,您实际上可以最大限度地提高实现更高并发度的机会,因为多个读者可以一起进行,因此这比使用普通的旧同步方法更好。 然而,相反的是在问题说明,代码不允许一个作家,而读者阅读写作。 但这本身并不一定是个问题。

读者在继续之前获得了读锁定。 在继续之前,编写器获取写锁定。 读锁的规则允许在没有写锁时获取一个(但是如果有一些读锁,即如果有更多活动读取器则可以)。 当且仅当没有其他锁(没有读者,没有编写者)时,写锁的规则允许获取一个。 因此允许多个读者,但只允许一个作者。

可能需要的唯一更改是将锁定初始化代码更改为:

private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

由于问题中给出的原始代码不要求锁是公平的。 通过上述更改,可以保证“线程使用近似到达顺序策略争用入口。当释放写入锁定时,最长等待的单个写入器将被分配写入锁定,或者如果读取器等待的时间长于任何作家,读者都将获得读锁定。当构造为非公平时,进入锁定的顺序不必是到达顺序。“ (摘自http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html

另请参阅以下内容(来自同一来源):

ReentrantReadWriteLocks可用于在某些类型的集合的某些用途中提高并发性。 这通常是值得的,只有当预期集合很大时,由更多的读取器线程访问而不是编写器线程,并且需要具有超过同步开销的开销的操作。 例如,这是一个使用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(); }
   }
 }

API文档的摘录特别注重性能。 在您的具体情况下,我无法评论您是否符合“大集合”标准,但我可以说输出到控制台比线程安全机制开销更耗时。 无论如何,从逻辑的角度来看,使用ReentrantReadWriteLocks是完全合理的,并且完全是线程安全的。 这是很好的代码:-)

注1(回答原问题评论中的异常问题):取自http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/Lock.html lock()获取锁。 如果锁定不可用,则当前线程将被禁用以进行线程调度,并且在获取锁定之前处于休眠状态。

Lock实现可能能够检测到锁的错误使用,例如可能导致死锁的调用,并且可能在这种情况下抛出(未经检查的)异常。 必须通过Lock实现记录环境和异常类型。

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

注意2:虽然对StringBuilder的访问受锁的保护,但System.out却没有。 特别是,多个读取器可以同时读取该值并尝试同时输出它。 这也没关系,因为对System.out.println()的访问是同步的。

注3:如果你想禁止多个活动的编写器,但允许一个编写器和一个或多个读者同时处于活动状态,你可以简单地跳过使用读锁,即删除lock.readLock()。lock(); 和lock.readLock()。unlock(); 在你的代码中。 但是,在这种特殊情况下,这是错误的。 您需要停止并发读取和写入StringBuilder。

描述和代码似乎是两个不同的东西。 你在描述中说,你想让读者在作者(我一次假设一个人)写的时候读。 但是,您也可以锁定读取方法。 所以现在你有一个,读者或作者一次访问你的缓冲区。

如果您希望在有编写器时让读者访问,请从read方法中删除锁定。

暂无
暂无

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

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