读写锁,用于文件的并发访问

huangapple go评论59阅读模式
英文:

ReadWriteLock for concurrency access to file

问题

我有一个在并发环境中实现文件读写操作的类。我知道BufferedInputStreamBufferedWriter是同步的,但在我的情况下,读写操作可以同时使用。现在我使用ReentrantReadWriteLock,但我对解决方案是否正确不太有信心。

public class FileSource {

    private final File file;

    private final ReadWriteLock lock;

    public FileWrapper(final File file) {
        if (Objects.isNull(file)) {
            throw new IllegalArgumentException("File can't be null!");
        }
        this.file = file;
        this.lock = new ReentrantReadWriteLock();
    }

    public String getContent() {
        final Lock readLock = lock.readLock();
        readLock.lock();

        final StringBuilder sb = new StringBuilder();
        try (final BufferedInputStream in =
                     new BufferedInputStream(
                             new FileInputStream(file))) {
            int data;
            while ((data = in.read()) > 0) {
                sb.append(data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
        return sb.toString();
    }

    public void saveContent(final String content) {
        final Lock writeLock = lock.writeLock();
        writeLock.lock();

        try (BufferedWriter out =
                     new BufferedWriter(
                             new FileWriter(file))) {
            out.write(content);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }
}

在这种情况下,ReentrantReadWriteLock是正确的解决方案,因为它允许多个线程同时获得读锁,但只有一个线程可以获得写锁。这与你的要求相符,因为你希望在并发环境中允许同时进行读和写操作,但写操作之间必须是互斥的,以避免数据不一致性和竞争条件。ReentrantLock也可以实现类似的效果,但是相对于读写锁,它可能会导致读操作的并发性能有所下降,因为它不支持多个线程同时获得读锁。

请注意,你的代码中存在一些小错误,例如类名应该是FileSource而不是FileWrapper,以及read()方法返回的是一个字节数据而不是字符数据,可能会导致不正确的文本内容。另外,对于字符数据的读写,你应该使用InputStreamReaderOutputStreamWriter来处理字符编码。

英文:

I have a class which implements read and write operation to file in a concurrent environment. I know BufferedInputStream and BufferedWriter are synchronized but in my case read and write operations can be used simultaneously. Now I use ReentrantReadWriteLock but I'm not confident about a solution correctly.

public class FileSource {
private final File file;
private final ReadWriteLock lock;
public FileWrapper(final File file) {
if (Objects.isNull(file)) {
throw new IllegalArgumentException("File can't be null!");
}
this.file = file;
this.lock = new ReentrantReadWriteLock();
}
public String getContent() {
final Lock readLock = lock.readLock();
readLock.lock();
final StringBuilder sb = new StringBuilder();
try (final BufferedInputStream in =
new BufferedInputStream(
new FileInputStream(file))) {
int data;
while ((data = in.read()) > 0) {
sb.append(data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return sb.toString();
}
public void saveContent(final String content) {
final Lock writeLock = lock.writeLock();
writeLock.lock();
try (BufferedWriter out =
new BufferedWriter(
new FileWriter(file))) {
out.write(content);
} catch (IOException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
}

ReentrantReadWriteLock is the correct solution in this case or I need to use ReentrantLock or something else? (with a reason)

This discussion not about class design like File as a state or send File directly in the method or using nio package or ext. It shouldn't be a utility class. Method signatures and File as a field must stay without changes. It is about potential concurrency problems with File and InputStream\OutputStream.

答案1

得分: 1

RRWL在这里很好。当然,如果某些代码创建了一个new FileWrapper("/foo/bar.txt"), 然后其他一些代码也创建了一个单独的new FileWrapper("/foo/bar.txt"), 这两个包装器会互相干扰,导致问题出现;我假设你有某种外部机制来确保这种情况不会发生。如果没有的话,你可以考虑使用一些关于ConcurrentHashMap以及其并发方法(例如computeIfAbsent;对于这些不要使用普通的get/put)的理解,这可以帮助你解决问题。

需要注意的是,你的异常处理很糟糕。异常消息不应该以标点符号结尾(想想:如果没有这个规则,80%的异常消息会以感叹号结尾,会让日志文件变成一个有趣的练习),一般来说,如果你写了catch (Exception e) { e.printStackTrace(); },你会进入那个专门为在电影院大声说话的人和写下这种代码的人保留的特殊领域。

我会说一个名为saveContent的方法在抛出一些受检异常是合理的;毕竟,很明显它可能失败,调用它的代码可以合理地期望如果出现问题会采取一些措施。

如果你真的做不到,正确的catch块处理程序是:throw new RuntimeException("uncaught", e);。不是e.printStackTrace();。后者记录到一个无法控制的位置,删除了有用的信息,并且关键是,会继续运行,好像没有任何问题,默默地忽视了保存调用刚刚失败这一事实,而前者保留了所有信息,实际上会中止代码执行。这使得恢复变得困难,但至少比e.printStackTrace要容易,如果你希望更容易,那就制作一个特殊的异常。或者直接抛出那个IOException(你的代码会更简洁)。

这段代码中的另一个隐蔽的错误是它使用'平台默认字符集编码'来读取文件,这很少是你想要的。

新的Files API也可以一次性读取整个文件,在读取操作上节省了大量代码。随着你升级代码,你会受益于Files API对字符集编码的独特处理:与Java库中的大多数其他地方不同,如果你未指定字符集编码(而不是'平台默认',即'在生产中无法测试并且会浪费一周时间来追踪它的东西'),java.nio.file.Files会假设你想要使用UTF-8编码。

英文:

RRWL is fine here. Of course, if some code makes a new FileWrapper("/foo/bar.txt") and then some other code also makes a separate new FileWrapper("/foo/bar.txt") those two wrappers will be falling all over themselves and will cause things to go pearshaped; I assume you have some external mechanism to ensure this cannot happen. If you don't, some take on ConcurrentHashMap and its concurrency methods (such as computeIfAbsent; don't use the plain jane get/put for these) can help you out.

Note that your exception handling is bad. Exception messages should not end in punctuation (think about it: Without this rule, 80% of all exception messages would end in an exclamation mark and will make log files a fun exercise), and in general, if you ever write catch (Exception e) { e.printStackTrace(); }, you go to that special place reserved for people who talk in movie theaters, and people who write that.

I'd say a method called saveContent is justified in throwing some checked exception; after all, it's rather obvious that can fail, and code calling it can feasibly be expected to perhaps take some action if it does.

If you just can't get there, the proper ¯\(ツ)/¯ I dunno catch block handler is: throw new RuntimeException("uncaught", e);. Not e.printStackTrace();. The latter logs to an uncontrollable place, shaves off useful information, and crucially, keeps on running as if nothing is wrong, silently ignoring the fact that a save call just failed, whereas the former preserves all and will in fact abort code execution. It makes it hard to recover, but at least it's easier than e.printStackTrace, and if you wanted it to be easier, than make a special exception. Or just throw that IOException unmolested (you get way shorter code to boot!).

Another insiduous bug in this code is that it uses 'platform default charset encoding' to read your file, which is very rarely what you want.

The new Files API can also read the entire file in one go, saves you a ton of code on that read op. As you upgrade your code, you get the benefit of the Files API's unique take on charset encodings: Unlike most other places in the java libraries, java.nio.file.Files will assume you meant to use UTF-8 encoding if you fail to specify (instead of 'platform default', i.e. 'the thing that you cannot test for that will blow up in production and waste a week of your time chasing after it').

huangapple
  • 本文由 发表于 2020年9月30日 06:37:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/64128590.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定