Java中的ReentrantReadWriteLock

时间:2020-01-09 10:35:10  来源:igfitidea点击:

在本文中,我们将通过示例介绍java.util.concurrent.locks.ReadWriteLock接口的用法及其在Java中的实现类ReentrantReadWriteLock。

Java并发中的ReadWriteLock

顾名思义,ReadWriteLock具有一对关联的锁

  • 一种用于只读操作

  • 一种写操作

读锁和写锁的用法如下:

  • 只要不存在具有写锁访问权限的线程,读锁就可以同时由多个读取器线程持有。

  • 写锁是排他的。这意味着当线程获得写锁定时,没有线程获得读锁定或者写锁定。

ReadWriteLock的好处

同步线程的传统方式需要互斥锁。即使线程只是在读取共享资源(而不修改它),锁仍然是互斥的,即在资源被锁定时没有其他线程可以进入关键部分。

与互斥锁相比,读写锁在访问共享数据时允许更高级别的并发性。它的工作原理是,一次只能有一个线程(写线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取数据(因此是读取器线程),这可能有助于提高性能。多线程环境。

Java并发中的ReentrantReadWriteLock

Java中的ReentrantReadWriteLock类是ReadWriteLock接口的实现类。它以以下方式使用。

要创建一个ReentrantReadWriteLock

ReadWriteLock rwl = new ReentrantReadWriteLock();

为了获得阅读锁

Lock readLock = rwl.readLock();

为了获得写锁

Lock writeLock = rwl.writeLock();

在此请注意,ReadLock和WriteLock是ReentrantReadWriteLock类中的静态嵌套类,

  • ReentrantReadWriteLock.ReadLock –方法ReadWriteLock.readLock()返回的锁。

  • ReentrantReadWriteLock.WriteLock –方法ReadWriteLock.writeLock()返回的锁。

使用读锁定和写锁定进行锁定和解锁的步骤如下。
读锁

rwl.readLock().lock();
try {
  ..
  ..
}finally{
  rwl.readLock().unlock();
}

写锁

rwl.writeLock().lock();
try {
  ..
  ..
}finally{
  rwl.writeLock().lock();
}

如我们所见,ReentrantReadWriteLock遵循与Java中的ReentrantLock相同的约定,其中将对lock()方法的调用放在try块之前,然后再进行try-finally或者try-catch-finally块,然后使用finally块来调用unlock( ) 方法。

这样,只有在实际获取锁的情况下才调用unlock()方法,并且还可以确保在获取锁后如果有任何错误,则调用unlock()方法。

Java ReentrantReadWriteLock构造函数

  • ReentrantReadWriteLock()–使用默认(不公平)订购属性创建一个新的ReentrantReadWriteLock。

  • ReentrantReadWriteLock(boolean fair)–使用给定的公平性策略创建一个新的ReentrantReadWriteLock。

Java中的ReentrantReadWriteLock示例

在示例中,我们将有一个供多个线程使用的HashMap。将元素放入HashMap时,将获得写锁,因为它是修改操作。在使用get方法的情况下,将使用读取锁定,以便多个线程可以从HashMap获取值。然后,开始两个写线程和三个读线程以从HashMap放置和获取值。

public class RWLDemo {
  private final Map<String, String> numberMap = new HashMap<String, String>();
  private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  // get method with read lock
  public String get(String key) {
    System.out.println("Waiting to acquire lock in get method...");
    rwl.readLock().lock();
    System.out.println("Acquired read lock in get method");
    try { 
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
       }
       return numberMap.get(key); 
    }
    finally { 
      System.out.println("releasing read lock in get method ");
      rwl.readLock().unlock(); 
    }
	}
   
  // Put method with write lock
  public String put(String key, String value) {
    System.out.println("Waiting to acquire lock in put method...");
    rwl.writeLock().lock();
    System.out.println("Acquired write lock in put method");
    try { 
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      return numberMap.put(key, value); 
    }
    finally {
      System.out.println("Releasing write lock in put method ");
      rwl.writeLock().unlock(); 		  
    }
  }
   
	public static void main(String[] args) {
    RWLDemo rwlDemo = new RWLDemo();
    // To put some initial values in the Map
    rwlDemo.initialValueInMap();
    // Starting Three read threads and two write threads
    Thread wThread1 = new Thread(new WriterThread(rwlDemo, "3", "Three"));
    Thread rThread1 = new Thread(new ReadThread(rwlDemo, "1"));        
    Thread rThread2 = new Thread(new ReadThread(rwlDemo, "1"));
    Thread wThread2 = new Thread(new WriterThread(rwlDemo, "4", "Four"));
    Thread rThread3 = new Thread(new ReadThread(rwlDemo, "2"));

    wThread1.start();
    rThread1.start();
    rThread2.start();
    rThread3.start();
    wThread2.start();
  }

  private void initialValueInMap(){
    // Putting some values in the map
    numberMap.put("1", "One");
    numberMap.put("2", "Two");
  }
}

class ReadThread implements Runnable {
  RWLDemo rwDemo;
  String key;
  ReadThread(RWLDemo rwDemo, String key){
    this.rwDemo = rwDemo;
    this.key = key;
  }
  public void run() {
    System.out.println("Value - " + rwDemo.get(key));
  }
}

class WriterThread implements Runnable {
  RWLDemo rwDemo;
  String key;
  String value;
  WriterThread(RWLDemo rwDemo, String key, String value){
    this.rwDemo = rwDemo;
    this.key = key;
    this.value = value;
  }
  public void run() {
    rwDemo.put(key, value);
  }
}

输出:

Waiting to acquire lock in put method...
Waiting to acquire lock in put method...
Waiting to acquire lock in get method...
Waiting to acquire lock in get method...
Acquired read lock in get method
Waiting to acquire lock in get method...
Acquired read lock in get method
releasing read lock in get method 
Value - Two
releasing read lock in get method 
Acquired write lock in put method
Value - One
Releasing write lock in put method 
Acquired read lock in get method
releasing read lock in get method 
Acquired write lock in put method
Value - One
Releasing write lock in put method

我们可以从输出中看到,最初获取了两个读取线程,两个都可以访问锁定部分。一旦释放了读取锁,则仅获取写入锁,因为写入锁必须获得排他访问。还有另一个读锁,等待写锁释放写锁,然后仅获取读锁。

ReentrantReadWriteLock属性

  • 没有用于锁定访问的读取器或者写入器首选项顺序。但是,它确实支持可选的公平性政策。

  • 当ReentrantReadWriteLock构造为不公平(默认设置)时,未指定读取和写入锁的输入顺序。

  • 当ReentrantReadWriteLock构造为公平时,线程使用近似到达顺序策略争夺进入。释放当前持有的锁时,将为等待时间最长的单个写程序线程分配写锁定,或者如果有一组读取器线程的等待时间比所有等待的写程序线程的等待时间长,则将为该组分配读锁定。

  • 读取和写入锁定都可以以ReentrantLock样式重新获得读取或者写入锁定。请参阅此处的示例。

  • 重入还可以通过获取写锁,然后读锁和释放写锁的方式,从写锁降级为读锁。

  • 无法从读锁升级到写锁。