Java 经典的读写算法-使用单个锁有什么问题吗?
我问的是经典读者和作家的问题: 我最近正在研究这个问题,我编写了一个只使用一个锁的解决方案:Java 经典的读写算法-使用单个锁有什么问题吗?,java,multithreading,algorithm,Java,Multithreading,Algorithm,我问的是经典读者和作家的问题: 我最近正在研究这个问题,我编写了一个只使用一个锁的解决方案: class ReadWrite { private Object lock = new Object(); private boolean isWriting = false; private int readerCount = 0; public void read() throws InterruptedException { synchronize
class ReadWrite {
private Object lock = new Object();
private boolean isWriting = false;
private int readerCount = 0;
public void read() throws InterruptedException {
synchronized(lock) {
while (isWriting) {
lock.wait();
}
readerCount++;
}
doRead(); // takes some time to read it...
synchronized(lock) {
readerCount--;
lock.notifyAll();
}
}
public void write(int value) throws InterruptedException {
synchronized(lock) {
while (readerCount > 0 || isWriting) {
lock.wait();
}
isWriting = true;
}
doWrite(value); // takes some time to write it
synchronized(lock) {
isWriting = false;
lock.notifyAll();
}
}
private void doRead() { // omitted }
private void doWrite(int value) { // omitted }
}
。。然后我将创建一组线程,每个线程重复调用read()或write()
这解决了有利于读者的1st读者/作者问题
…然后我看到所有在线参考/书籍总是使用两个/多个锁;所以我不确定我是否遗漏了什么
我使用多线程配置对其进行了测试,它实际上运行良好,如下所示:
1) 同时进行多个读取
2) 在没有阅读的情况下,任何时候只有一位作家写作
我解决了第三个读者/作者问题(读者/作者之间的公平性),仅略微增加了上述解决方案,您可以看到/*编辑*/如下)
除了可能存在的效率差异外,您是否发现在算法方面存在任何错误,即对读卡器/写卡器使用单个锁的问题(至少对于第一个有利于读卡器的读卡器/写卡器问题)
编辑:
有一个关于我是否可以通过修改原文来解决第三读者/作者问题的讨论。。。并将其限制在2000ms以下(100ms读/写*20个线程),因此它就在这里;您可以取消对所有sysout的注释,以查看是否仍然发生独占写入以及同时读取。我还复制下面粘贴的时间运行输出
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.IntStream;
class ReadWrite {
private Object lock = new Object();
private boolean isWriting = false;
private int readerCount = 0;
Queue<Thread> order = new LinkedList<Thread>();
public void read(Reader r) throws InterruptedException {
long start = System.currentTimeMillis();
synchronized (lock) {
while (isWriting || (!order.isEmpty() && order.peek() != r)) {
if (!order.contains(r)) {
order.add(r);
}
lock.wait();
}
order.remove(r);
readerCount++;
lock.notifyAll();
// System.out.println("Reader " + r.id + " started reading; readerCount: " + readerCount);
}
doRead(r.id); // takes however many seconds to read it...
synchronized (lock) {
readerCount--;
// System.out.println("Reader " + r.id + " finished reading; readerCount: " + readerCount);
lock.notifyAll();
}
long delta = System.currentTimeMillis() - start;
System.out.println("Reader " + r.id + " delta: " + delta + "");
}
public void write(Writer w, int value) throws InterruptedException {
// System.out.println("Writer " + w.id + " entered write");
long start = System.currentTimeMillis();
synchronized (lock) {
while (readerCount > 0 || isWriting || (!order.isEmpty() && order.peek() != w)) {
if (!order.contains(w)) {
order.add(w);
}
lock.wait();
}
order.remove(w);
isWriting = true;
// System.out.println("Writer " + w.id + " started writing-----------");
}
doWrite(w.id, value);
synchronized (lock) {
isWriting = false;
// System.out.println("Writer " + w.id + " finished writing----------");
lock.notifyAll();
}
long delta = System.currentTimeMillis() - start;
System.out.println("Writer " + w.id + " delta: " + delta);
if (delta - 2000 > 10) {
System.out.println("!!!attention above!!!");
}
}
private void doRead(int readerId) {
LockSupport.parkNanos(100_000_000L);
}
private void doWrite(int writerId, int value) {
LockSupport.parkNanos(100_000_000L);
}
}
class Reader extends Thread{
public int id = 0;
private ReadWrite rw = null;
public Reader(int id, ReadWrite rw) {
this.id = id;
this.rw = rw;
}
@Override
public void run() {
while (true) {
try {
rw.read(this);
} catch (InterruptedException e) {
}
}
}
}
class Writer extends Thread {
public int id = 0;
private ReadWrite rw = null;
public Writer(int id, ReadWrite rw) {
this.id = id;
this.rw = rw;
}
@Override
public void run() {
while (true) {
try {
rw.write(this, 0);
} catch (InterruptedException e) {
}
}
}
}
public class RW {
public static void main(String[] args) throws InterruptedException {
ReadWrite rw = new ReadWrite();
for (int i : IntStream.range(0, 10).toArray()) {
new Reader(i, rw).start();
}
for (int i : IntStream.range(0, 10).toArray()) {
new Writer(i, rw).start();
}
}
}
因此它在最大期望时间下有界 您的代码似乎仍然不能解决第三个问题,并且优先考虑读者而不是作者。正如维基所说: 因此,有时会提出第三读写器问题,这增加了不允许任何线程饿死的约束;也就是说,获取共享数据锁的操作将始终在限定的时间内终止 让我们创建10个读线程和10个写线程:
public static void main(String[] args) throws InterruptedException {
ReadWrite rw = new ReadWrite();
for(int i : IntStream.range(0, 10).toArray()) {
new Thread(() -> {
while(true) {
try {
rw.read(i);
} catch (InterruptedException e) {}
}
}, "Reader #"+i).start();
}
for(int i : IntStream.range(0, 10).toArray()) {
new Thread(() -> {
while(true) {
try {
rw.write(i, i);
} catch (InterruptedException e) {}
}
}, "Writer #"+i).start();
}
}
让doRead
和doWrite
只需睡眠100毫秒:
private void doRead(int readerId) {
LockSupport.parkNanos(100_000_000L);
}
private void doWrite(int writerId, int value) {
LockSupport.parkNanos(100_000_000L);
}
让我们打印执行写入所需的时间:
public void write(int writerId, int value) throws InterruptedException {
long start = System.currentTimeMillis();
... all your code ...
long delta = System.currentTimeMillis()-start;
System.out.println("Write delta: "+delta);
}
我去掉了其他的指纹。现在我看到以下输出:
Write delta: 200
Write delta: 200
Write delta: 600
Write delta: 800
Write delta: 1000
Write delta: 1099
Write delta: 1300
Write delta: 1500
Write delta: 600
Write delta: 1899
...
Write delta: 2700
Write delta: 4399
Write delta: 2100
Write delta: 1900
...
Write delta: 2800
Write delta: 3400
Write delta: 6999
Write delta: 900
Write delta: 1300
Write delta: 2500
...
Write delta: 1800
Write delta: 2800
Write delta: 7802
Write delta: 2600
...
我预计最大增量大约为2000毫秒:我们有20个并发线程,每个线程运行大约需要100毫秒,因此我们最多应该等待所有其他线程完成当前操作。但我们不应该等待其他线程多次。实际上,当我用标准JDK
ReentrantReadWriteLock
替换您的实现时,我观察到了最大增量2000-2100。在您的情况下,延迟可能要长好几倍,而且似乎没有保证最大值(从长远来看,我也观察到更高的延迟)。因此,我不会说您的代码解决了第三个问题。我会说,通过检查readerCount
和isWriting
您实际上实现了额外的锁。我在问题中说,我发布的代码只解决了第一个读写器问题。我确实修改了代码,可以解决第三个问题。下面链接中的一个(我修改的代码)实际上解决了最多第三个读写器问题:(您也可以在线查看输出)我还可以编写一个解决最多第二个问题的解决方案。@user3101492,好的,我重写了答案。您可以提供IDEOne链接,但请尝试将所有相关代码直接发布到问题中。它不是很大,所以它非常适合它。好吧,如果你想更严格地限制饥饿,我可以像ReentrantReadWriteLock那样做;它的源代码确保一个线程不会比后面的另一个线程等待更长的时间。是的,我在这里做到了:仍然使用一个锁,只是使用一个简单的队列进行订单;我也会把这个贴在问题上。。是的,通过这种方式,它在2000ms以下得到限制,同时读卡器和独占写入仍然会发生。我最初的问题实际上是关于它是否解决了第一个问题(我甚至已经说过了),但由于我们已经走了这么远,我更新了原始答案,包括第三个读者/作者soln(有更严格的限制)和时间outputs@user3101492,那么现在我没有足够的专业知识来说明您的解决方案是否有任何缺点。
Write delta: 200
Write delta: 200
Write delta: 600
Write delta: 800
Write delta: 1000
Write delta: 1099
Write delta: 1300
Write delta: 1500
Write delta: 600
Write delta: 1899
...
Write delta: 2700
Write delta: 4399
Write delta: 2100
Write delta: 1900
...
Write delta: 2800
Write delta: 3400
Write delta: 6999
Write delta: 900
Write delta: 1300
Write delta: 2500
...
Write delta: 1800
Write delta: 2800
Write delta: 7802
Write delta: 2600
...