pet*_*ter 8 java concurrency multithreading locking thread-safety
我正在尝试实现一个读/写缓冲区类,它可以支持多个写入器和读取器,并且读取器可以在写入缓冲区时同时读取缓冲区.这是我的代码,到目前为止我还没有看到任何问题,但我不能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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
多线程安全无任何问题!读写锁可以保护对StringBuilder的访问,代码简洁易读.
通过使用ReentrantReadWriteLock,您实际上可以最大限度地提高实现更高并发度的机会,因为多个读者可以一起进行,因此这比使用普通的旧同步方法更好.然而,相反的是在问题说明,代码中并没有让一个作家,而读者阅读写作.但这本身并不一定是个问题.
读者在继续之前获得了读锁定.在继续之前,编写器获取写锁定.读锁的规则允许在没有写锁时获取一个(但是如果有一些读锁,即如果有更多活动读取器则可以).当且仅当没有其他锁(没有读者,没有编写者)时,写锁的规则允许获取一个.因此允许多个读者,但只允许一个作者.
可能需要的唯一更改是将锁定初始化代码更改为:
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
Run Code Online (Sandbox Code Playgroud)
由于问题中给出的原始代码不要求锁是公平的.通过上述更改,可以保证"线程使用近似到达顺序策略争用入口.当释放写入锁定时,最长等待的单个写入器将被分配写入锁定,或者如果读取器等待的时间长于任何作家,读者都将获得读锁定.当构造为非公平时,进入锁定的顺序不必是到达顺序." (来自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(); }
}
}
Run Code Online (Sandbox Code Playgroud)
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.