多线程文件并发操作相关题目解析
Posted 码农每日一题
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程文件并发操作相关题目解析相关的知识,希望对你有一定的参考价值。
问:Java 多线程文件读写操作怎么保证并发安全?
答:多线程文件并发安全其实就是在考察线程并发安全,最锉的方式就是使用 wait/notify、Condition、synchronized、ReentrantLock 等方式,这些方式默认都是排它操作(排他锁),也就是说默认情况下同一时刻只能有一个线程可以对文件进行操作,所以可以保证并发文件操作的安全性,但是在并发读数量远多于写数量的情况下性能却不那么好。因此推荐使用 ReadWriteLock 的实现类 ReentrantReadWriteLock,它也是 Lock 的一种实现,允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。所以相对于排他锁来说提高了并发效率。ReentrantReadWriteLock 读写锁里面维护了两个继承自 Lock 的锁,一个用于读操作(ReadLock),一个用于写操作(WriteLock)。
下面给出高效读写操作的样例:
class DataCache {
private Map<String, String> cachedMap = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
public long readSize() {
try {
readLock.lock();
mockTimeConsumingOpt();
return cachedMap.size();
} finally {
readLock.unlock();
}
}
public long write(String key, String value) {
try {
writeLock.lock();
mockTimeConsumingOpt();
cachedMap.put(key, value);
return cachedMap.size();
} finally {
writeLock.unlock();
}
}
private void mockTimeConsumingOpt() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Reader extends Thread {
public DataCache dataCache;
public Reader(String name, DataCache dataCache) {
super(name);
this.dataCache = dataCache;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
long result = dataCache.readSize();
System.out.println(name + " read current cache size is:" + result);
}
}
class Writer extends Thread {
public DataCache dataCache;
public Writer(String str, DataCache dataCache) {
super(str);
this.dataCache = dataCache;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
String result = "" + dataCache.write(name, "DATA-"+name);
System.out.println(name + " write to current cache!");
}
}
public class Test {
public static void main(String[] args) {
final DataCache dataCache = new DataCache();
ArrayList<Thread> worker = new ArrayList<>();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
Writer writer = new Writer("Writer"+i, dataCache);
worker.add(writer);
} else {
Reader reader = new Reader("Reader"+i, dataCache);
worker.add(reader);
}
}
for (int i = 0; i < worker.size(); i++) {
worker.get(i).start();
}
}
}
上面示例使用 ReentrantReadWriteLock 来提高了 Collection 的并发效率,所以常常用在读线程访问数量多于写线程访问数量的情况下。
此外还可以利用 ReentrantReadWriteLock 的重入特性来执行升级缓存后的锁降级操作,具体场景案例如 API 中伪代码,如下:
class CachedData {
Object data;
//缓存是否有效
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
//获取读锁
rwl.readLock().lock();
//如果缓存无效则更新cache,否则直接使用data
if (!cacheValid) {
// Must release read lock before acquiring write lock
//获取写锁前须先释放读锁
rwl.readLock().unlock();
rwl.writeLock().lock();
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
//锁降级操作!在释放写锁前需先获取读锁
rwl.readLock().lock();
rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);
//释放读锁
rwl.readLock().unlock();
}
}
通过上面案例可以发现 ReentrantReadWriteLock 支持公平和非公平锁;还支持可重入特性,读线程在获取了读锁后还可以获取读锁,写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;此外还允许从写入锁降级为读取锁(操作方式是先获取写入锁,然后获取读取锁,最后释放写入锁),但是不允许从读取锁升级到写入锁,因为可能会出现数据不一致问题。
《》
《》
《》
看完不过瘾?看完还想看?那就点击左下角阅读原文查看本号历史经典技术知识点题目推送,解锁更多基础知识~
以上是关于多线程文件并发操作相关题目解析的主要内容,如果未能解决你的问题,请参考以下文章