我正在尝试为被多个线程并发访问的资源实现一个简单的读写锁。工作线程随机尝试读取或写入共享对象。当设置读锁时,工作线程在解除锁定之前不能进行写操作。当设置写锁时,不允许读取和写入。
虽然我的实现似乎有效,但我认为它在概念上是错误的。
正在进行的读操作应该允许同时发生更多的读操作,从而导致读取的总数大于写入的数量。我的程序产生的数字遵循由工作者执行这些操作的概率。
我觉得我的实现实际上根本不是并发的,但我很难找出错误。我真的很希望能指点一下正确的方向。
调度和终止工作线程的主类:
资源类(Resource class):
正在进行的读操作应该允许同时发生更多的读操作,从而导致读取的总数大于写入的数量。我的程序产生的数字遵循由工作者执行这些操作的概率。
我觉得我的实现实际上根本不是并发的,但我很难找出错误。我真的很希望能指点一下正确的方向。
调度和终止工作线程的主类:
class Main {
private static final int THREAD_NUMBER = 4;
public static void main(String[] args) {
// creating workers
Thread[] workers = new Thread[THREAD_NUMBER];
for (int i = 0; i < THREAD_NUMBER; i++) {
workers[i] = new Thread(new Worker(i + 1));
}
System.out.println("Spawned workers: " + THREAD_NUMBER);
// starting workers
for (Thread t : workers) {
t.start();
}
try {
Thread.sleep((long) 10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// stopping workers
System.out.println("Stopping workers...");
for (Thread t : workers) {
t.interrupt();
}
}
}
资源类(Resource class):
class Resource {
enum ResourceLock {
ON,
OFF
}
private static Resource instance = null;
private ResourceLock writeLock = ResourceLock.OFF;
private ResourceLock readLock = ResourceLock.OFF;
private Resource() {}
public static synchronized Resource getInstance() {
if (instance == null) {
instance = new Resource();
}
return instance;
}
public ResourceLock getWriteLock() {
return writeLock;
}
public ResourceLock getReadLock() {
return readLock;
}
public void setWriteLock() {
writeLock = ResourceLock.ON;
}
public void setReadLock() {
readLock = ResourceLock.ON;
}
public void releaseWriteLock() {
writeLock = ResourceLock.OFF;
}
public void releaseReadLock() {
readLock = ResourceLock.OFF;
}
}
最后是Worker类:
import java.util.Random;
class Worker implements Runnable {
private static final double WRITE_PROB = 0.5;
private static Random rand = new Random();
private Resource res;
private int id;
public Worker(int id) {
res = Resource.getInstance();
this.id = id;
}
public void run() {
message("Started.");
while (!Thread.currentThread().isInterrupted()) {
performAction();
}
}
private void message(String msg) {
System.out.println("Worker " + id + ": " + msg);
}
private void read() {
synchronized(res) {
while (res.getWriteLock() == Resource.ResourceLock.ON) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
res.setReadLock();
// perform read
try {
Thread.sleep((long) 500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
res.releaseReadLock();
res.notifyAll();
}
message("Finished reading.");
}
private void write() {
synchronized(res) {
while (res.getWriteLock() == Resource.ResourceLock.ON || res.getReadLock() == Resource.ResourceLock.ON) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
res.setWriteLock();
// perform write
try {
Thread.sleep((long) 500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
res.releaseWriteLock();
res.notifyAll();
}
message("Finished writing.");
}
private void performAction() {
double r = rand.nextDouble();
if (r <= WRITE_PROB) {
write();
} else {
read();
}
}
}
为什么要分别使用读锁和写锁呢?这是因为我想同时原子化锁定操作和其查询。
以下是在0.5写入概率下的输出示例:
Spawned workers: 4
Worker 2: Started.
Worker 3: Started.
Worker 1: Started.
Worker 4: Started.
Worker 2: Finished writing.
Worker 4: Finished reading.
Worker 1: Finished writing.
Worker 3: Finished writing.
Worker 1: Finished reading.
Worker 4: Finished writing.
Worker 2: Finished reading.
Worker 4: Finished reading.
Worker 1: Finished reading.
Worker 3: Finished writing.
Worker 1: Finished writing.
Worker 4: Finished writing.
Worker 2: Finished writing.
Worker 4: Finished writing.
Worker 1: Finished reading.
Worker 3: Finished writing.
Worker 1: Finished writing.
Worker 4: Finished reading.
Worker 2: Finished writing.
Stopping workers...
Worker 4: Finished writing.
Worker 1: Finished writing.
Worker 3: Finished reading.
Worker 2: Finished reading.
非常感谢您的帮助。
synchronize
访问,则会有一个独占锁。您必须从头开始构建自己的锁定机制。 - Ben Manessynchronized
块内,就不可能有并发。一旦将操作移出synchronized
块,它就会中断,因为每个读取器在结束时都会执行readLock = ResourceLock.OFF
,无论有多少读取器。这在概念层面上就行不通了。您需要一个计数器来记住读取器的数量以支持多个读取器。除此之外,您应该将逻辑放入Resource
类中,而不是使其成为白盒结构,并希望调用者正确实现逻辑。 - Holger