在Java中实现资源读/写锁

时间:2018-03-19 21:32:26

标签: java concurrency locking synchronized

我试图为多个线程并发访问的资源实现简单的读/写锁。工作人员随机尝试读取或写入共享对象。设置读锁定后,工作人员在锁定释放之前不应该写入。设置写锁定时,不允许读写。 虽然我的实现似乎有效,但我认为这在概念上是错误的。

发生读取操作应该允许同时发生更多读取操作,导致读取的总数大于写入次数。我的程序产生的数字跟随工人执行这些操作的概率。

我觉得我的实现实际上根本不是并发的,但我很难识别错误。我真的很感激被指向正确的方向。

派遣和终止工人的主要班级:

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();
        }
    }
}

资源类:

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;
    }
}

最后是工人阶级:

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.

非常感谢。

2 个答案:

答案 0 :(得分:4)

您正在synchronized块内执行整个操作,因此没有并发性。此外,没有任何锁类型的优先级,因为至多一个线程可以拥有锁。不在synchronized块中执行整个操作将无法使用您当前的代码,因为每个读者最后都会readLock = ResourceLock.OFF,无论有多少读者。没有计数器,您无法正确支持多个读者。

除此之外,它是一个奇怪的代码结构,提供一个Resource类维护状态,但完全由调用者完成,以便用它做正确的事情。这不是处理责任和封装的方式。

实现可能看起来像

class ReadWriteLock {
    static final int WRITE_LOCKED = -1, FREE = 0;

    private int numberOfReaders = FREE;
    private Thread currentWriteLockOwner;

    public synchronized void acquireReadLock() throws InterruptedException {
        while(numberOfReaders == WRITE_LOCKED) wait();
        numberOfReaders++;
    }
    public synchronized void releaseReadLock() {
        if(numberOfReaders <= 0) throw new IllegalMonitorStateException();
        numberOfReaders--;
        if(numberOfReaders == FREE) notifyAll();
    }
    public synchronized void acquireWriteLock() throws InterruptedException {
        while(numberOfReaders != FREE) wait();
        numberOfReaders = WRITE_LOCKED;
        currentWriteLockOwner = Thread.currentThread();
    }
    public synchronized void releaseWriteLock() {
        if(numberOfReaders!=WRITE_LOCKED || currentWriteLockOwner!=Thread.currentThread())
            throw new IllegalMonitorStateException();
        numberOfReaders = FREE;
        currentWriteLockOwner = null;
        notifyAll();
    }
}

它只使用获取的读锁的计数器,当存在写锁时将计数器设置为-1(因此写锁不能嵌套)。只要没有写锁定,获取读锁定就可以成功,因此不需要为它们实现优先级,当另一个线程已经具有实际锁定时成功的可能性就足够了。事实上,当读者的数量远远超过作者时,您可能会遇到“starving writer” problem

工人简化为

class Worker implements Runnable {
    private static final double WRITE_PROB = 0.5;
    private static final Random rand = new Random();
    private final ReadWriteLock theLock;
    private final int id;

    public Worker(int id, ReadWriteLock lock) {
        theLock = lock;
        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() {
        try {
            theLock.acquireReadLock();
        } catch(InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
        // perform read
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        finally { theLock.releaseReadLock(); }
        message("Finished reading.");
    }

    private void write() {
        try {
            theLock.acquireWriteLock();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
        // perform write
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        finally { theLock.releaseWriteLock(); }
        message("Finished writing.");
    }

    private void performAction() {
        double r = rand.nextDouble();
        if (r <= WRITE_PROB) {
            write();
        } else {
            read();
        }
    }
}

请注意,我在这里避免使用全局变量。锁应该传递给构造函数。在锁定获取期间中断时方法返回也很重要。在原始代码中自我中断和重试采集将导致无限循环,因为在恢复当前线程的中断状态后,下一次等待将再次抛出InterruptedException。当然,在没有锁定的情况下进行操作也是错误的,因此唯一有效的选项不是恢复中断状态或立即返回。

对主程序的唯一更改是构造一个传递锁实例:

ReadWriteLock sharedLock = new ReadWriteLock();
// creating workers
Thread[] workers = new Thread[THREAD_NUMBER];
for (int i = 0; i < THREAD_NUMBER; i++) {
    workers[i] = new Thread(new Worker(i + 1, sharedLock));
}
System.out.println("Spawned workers: " + THREAD_NUMBER);

// starting workers
for (Thread t : workers) {
    t.start();
}
try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

// stopping workers
System.out.println("Stopping workers...");
for (Thread t : workers) {
    t.interrupt();
}

答案 1 :(得分:0)

这是ReadWriteLock的简单实现,具有更高的写操作优先级:

public class ReadWriteLock{

  private int readers       = 0;
  private int writers       = 0;
  private int writeRequests = 0;

  public synchronized void lockRead() throws InterruptedException{
    while(writers > 0 || writeRequests > 0){
      wait();
    }
    readers++;
  }

  public synchronized void unlockRead(){
    readers--;
    notifyAll();
  }

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;

    while(readers > 0 || writers > 0){
      wait();
    }
    writeRequests--;
    writers++;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    writers--;
    notifyAll();
  }
}

来源:http://tutorials.jenkov.com/java-concurrency/read-write-locks.html