ReadWriteLock的作用:防止异常还是避免多线程读写数据混乱?
多线程文件并发读写:为什么没抛异常?ReadWriteLock到底管啥?
先直接给你答案:ReadWriteLock主要是用来避免多线程互相干扰导致的数据错乱(比如文本乱码、内容拼接错误),而不是单纯为了防止异常。你的测试没抛异常只是巧合,不代表这种写法安全。
为啥你的代码没炸?
Java的FileWriter和FileReader本身完全不是线程安全的,但没抛出异常不代表没问题:
- 底层操作系统的文件操作有一点点原子性保障(比如单次写少量数据时,OS可能帮你"扛"了一部分冲突),但这完全依赖系统实现,不是Java给你的承诺。
- 你每次只写固定的
"foo\n"还调用了flush(),这种场景下冲突概率比较低,但不代表不会出现——要是你多跑几次测试,或者把写入内容改长、线程数调大,大概率会看到文件里的内容乱掉,甚至可能出现IO异常(只是你没碰到而已)。
ReadWriteLock的核心价值
ReadWriteLock是读写分离的锁,它解决的核心问题是数据一致性和读写性能平衡:
- 写操作必须互斥:同一时间只能有一个线程写,不然两个线程同时写的内容会互相穿插、覆盖,比如本来要写两行
"foo",结果变成"ffooo"或者其他乱码。 - 读操作可以共享:多个线程同时读不会互相阻塞,这在读多写少的场景下比单纯用
synchronized效率高很多。 - 它的目标是保证你读写的内容是"正确"的,而不是为了抓异常——异常只是并发冲突可能带来的副作用之一,不是全部。
你的代码里的坑
看你的测试代码,还有几个明显的线程不安全点:
- 所有线程共享同一个
FileWriter和FileReader实例,这俩类根本没考虑多线程场景,就算没抛异常,也极大概率出现:- 写的时候内容乱序拼接,比如两个线程的
"foo"粘在一起。 - 读的时候读到半拉内容,比如一个线程刚写了
"fo",另一个线程就把这俩字符读走了。
- 写的时候内容乱序拼接,比如两个线程的
- 所有线程跑完才关流,这也有风险——万一某个线程还在操作流,你就给关了,说不定下次跑就出异常了。
怎么改才安全?
要做安全的并发文件读写,必须加锁:
- 用
ReadWriteLock(或者synchronized,但ReadWriteLock在多读场景下更高效)来包裹读写操作:- 写的时候拿写锁,写完立刻释放(一定要放finally里,防止异常导致锁没释放)。
- 读的时候拿读锁,读完释放。
给你改个简单的例子,把Worker类加上锁:
static class Worker implements Runnable { private final Reader rdr; private final Writer wrtr; private final int nmbRdWrt, maxWaitTime; private final ReadWriteLock rwLock; public Worker(Reader rdr, Writer wrtr, int nmbRdWrt, int maxWaitTime, ReadWriteLock rwLock) { this.rdr = rdr; this.wrtr = wrtr; this.nmbRdWrt = nmbRdWrt; this.maxWaitTime = maxWaitTime; this.rwLock = rwLock; } public void run() { try { for(int i = 0; i < nmbRdWrt; i++) { rndmPause(maxWaitTime); // 写操作加写锁,确保同一时间只有一个线程写 rwLock.writeLock().lock(); try { wrtr.write("foo" + System.getProperty("line.separator")); wrtr.flush(); } finally { rwLock.writeLock().unlock(); } rndmPause(maxWaitTime); // 读操作加读锁,允许多个线程同时读 rwLock.readLock().lock(); try { char[] cbuf = new char[100]; rdr.read(cbuf); } finally { rwLock.readLock().unlock(); } } } catch(Exception e) { e.printStackTrace(); } } }
然后在Main方法里初始化锁,传给每个Worker:
ReadWriteLock rwLock = new ReentrantReadWriteLock(); // 创建Worker时把锁传进去 Thread t = new Thread(new Worker(rdr, wrtr, nmbOfReadWritePerThread, maxWaitTimeBetweenReadWrite, rwLock));
这样改完,就能保证读写操作的安全性,不会出现内容错乱,也能降低异常出现的概率。
内容的提问来源于stack exchange,提问作者Just Someone
相关产品推荐
相关产品推荐

