ReadWriteLock

208 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

一、简介

ReadWriteLock维护一对关联的 Lock,一个用于只读操作,一个用于写入。 当没有线程持有写锁时,readLock (读锁) 可以被多个读线程同时持有, writeLock (写锁) 是独占的。

与互斥锁相比,读写锁在访问共享数据时允许更高级别的并发性,虽然一次只有一个线程可以修改共享数据,但在许多情况下,任意数量的线程可以同时读取数据,读写锁更适用于读多写少的场景。

二、使用

ReadWriteLock 是一个接口。 ReadWriteLock 由 java.util.concurrent.locks 包中的 ReentrantReadWriteLock 类实现。 因此,要使用 ReadWriteLock,我们必须使用 ReentrantReadWriteLock。

规则:

读锁和写锁,允许线程锁定 ReadWriteLock 以进行读取或写入。

  1. 读锁: 如果没有线程请求读锁和写锁,那么多个线程可以锁读锁。这意味着多个线程可以同时读取数据,只要没有线程写入数据或更新数据。
  2. 写锁: 如果没有线程在写或读,那么一次只有一个线程可以锁定写锁。其他线程必须等到锁被释放。这意味着,此时只有一个线程可以写入数据,其他线程必须等待。

方法: ReadWritelock 提供了两种方法:

  1. 锁定 readLock()
  2. 锁定 writeLock()

他们的工作与他们的名字相似。readLock() 用于在读取时获取锁:

readLock = rwLock.readLock();

对执行读操作的代码块使用读锁:

readLock.lock();
try {
    // statements
}
finally {
    readLock.unlock();
}

writeLock() 用于在写入时获取锁:

锁定 writeLock = rwLock.writeLock();

在执行写操作的代码块上使用写锁:

writeLock.lock();
try {
    statements to write the data
    }
finally {
    writeLock.unlock();
}

使用ReentrantReadWriteLock实现一个集合线程安全的List集合

// Implementation of ReadWriteLock in Java
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class GFG<O> {
    
    private final ReadWriteLock readWriteLock  = new ReentrantReadWriteLock();
    
    private final Lock writeLock= readWriteLock.writeLock();
    
    private final Lock readLock = readWriteLock.readLock();
    
    private final List<O> list = new ArrayList<>();
    
    // setElement function sets
    // i.e., write the element to the thread
    public void setElement(O o)
    {
        // acquire the thread for writing
        writeLock.lock();
        try {
            list.add(o);
            System.out.println(
                "Element by thread "
                + Thread.currentThread().getName()
                + " is added");
        }
        finally {
            // To unlock the acquired write thread
            writeLock.unlock();
        }
    }
    
    // getElement function prints
    // i.e., read the element from the thread
    public O getElement(int i)
    {
        // acquire the thread for reading
        readLock.lock();
        try {
            System.out.println(
                "Elements by thread "
                + Thread.currentThread().getName()
                + " is printed");
            return list.get(i);
        }
        finally {
            // To unlock the acquired read thread
            readLock.unlock();
        }
    }
    public static void main(String[] args)
    {
        GFG<String> gfg = new GFG<>();
        
        gfg.setElement("Hi");
        gfg.setElement("Hey");
        gfg.setElement("Hello");
        
        System.out.println("Printing the last element : "
                           + gfg.getElement(2));
    }
}

三、总结

读写锁是否会比使用互斥锁提高性能取决于数据被读取与被修改相比的频率、读写操作的持续时间以及对数据的争用data - 即同时尝试读取或写入数据的线程数。例如,最初填充了数据并随后不经常修改但经常被搜索(例如某种目录)的集合是使用读写锁的理想候选者。但是,如果更新变得频繁,则数据大部分时间都被排他锁定,并且并发性几乎没有增加。此外,如果读取操作太短,则读写锁实现的开销(本质上比互斥锁更复杂)会主导执行成本,特别是因为许多读写锁实现仍然通过一个一小段代码。最终,只有分析和测量才能确定读写锁的使用是否适合您的应用程序。