多线程文件并发操作相关题目解析

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 支持公平和非公平锁;还支持可重入特性,读线程在获取了读锁后还可以获取读锁,写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;此外还允许从写入锁降级为读取锁(操作方式是先获取写入锁,然后获取读取锁,最后释放写入锁),但是不允许从读取锁升级到写入锁,因为可能会出现数据不一致问题。

《》

《》

《》


看完不过瘾?看完还想看?那就点击左下角阅读原文查看本号历史经典技术知识点题目推送,解锁更多基础知识~

以上是关于多线程文件并发操作相关题目解析的主要内容,如果未能解决你的问题,请参考以下文章

Java 多线程并发运用:解析单个大文件入库

线程学习知识点总结

操作系统:进程与线程大解析

操作系统:进程与线程大解析

java 并发相关

多个用户访问同一段代码