读写锁

概述

事实上,咱们之前学习的锁其实都有一点小小的问题,那就是其锁的粒度太大了,像是Synchronized 和 ReentrantLock 都是独占锁,即在同一时间只要一个线程获取到锁。而许多情况下,咱们的许多操作并不是互斥的,完全可以支撑并行履行,并不需要加锁来进行约束,十分的不灵活,那么有没有一个能够让咱们更加自在的挑选加锁的办法呢?

ReentrantReadWriteLock读写锁,望文生义是一把锁分为两部分:读锁写锁其中读锁允许多个线程一起取得,是一个共享锁,由于读操作本身是线程安全的;而写锁则是排他锁,不允许多个线程一起取得写锁,并且写操作和读操作也是互斥的

主要处理:对共享资源有读和写的操作,且写操作没有读操作那么频频的场景。

特点

  • 公正性:读写锁支撑非公正和公正的锁获取办法,非公正锁的吞吐量优于公正锁的吞吐量,默许结构的是非公正锁
  • 可重入:在线程获取读锁之后能够再次获取读锁,可是不能获取写锁,而线程在获取写锁之后能够再次获取写锁,一起也能获取读锁
  • 锁降级:线程获取写锁之后获取读锁,再开释写锁,这样完成了写锁变为读锁,也叫锁降级

其更适合读多写少的场景。读锁可以被多个线程一起持有, 所以并发才干很高而写锁是独占锁, 在一个线程持有写锁时候, 其他线程都不能在抢占, 包括抢占读锁都会阻塞

使用

常用办法

办法 描绘
public ReentrantReadWriteLock() 默许结构办法,非公正锁
public ReentrantReadWriteLock(boolean fair) true 为公正锁
public ReentrantReadWriteLock.ReadLock readLock() 回来读锁
public ReentrantReadWriteLock.WriteLock writeLock() 回来写锁
public void lock() 加锁
public void unlock() 解锁
public boolean tryLock() 测验获取锁

现在有个餐厅,其生意非常好,客人特别多,于是其要求来的客人必须先抢票才干进来用餐。于是有很多人想来用餐的就要去抢票,一起还有许多吃瓜大众看还有多少票剩余。咱们来完成这个案例

public class Restaurant {
​
  private volatile int num = 6;
​
  private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
​
  public void grabTickets() {
​
    boolean isGet = false;
​
    //获取写锁
    rwLock.writeLock().lock();
    
    //锁降级,获取读锁
    rwLock.readLock().lock();
    
    try {
      if (num > 0) {
        --num;
        isGet = true;
       } else {
        System.out.println(Thread.currentThread().getName() + "没票了");
       }
     } finally {
      //开释写锁
      rwLock.writeLock().unlock();
     }
    
    try {
      if (isGet) {
        System.out.println(Thread.currentThread().getName() + "抢到了票,现在还剩余的票数为:" + num);
       }
     } finally {
      //开释读锁
      rwLock.readLock().unlock();
     }
   }
​
  public void getNum() {
    rwLock.readLock().lock();
​
    try {
      System.out.println(Thread.currentThread().getName() + "检查了现在的票数:" + num);
     } finally {
      rwLock.readLock().unlock();
     }
​
   }
​
}

咱们这里在抢票时,为了其他线程可以更快地检查当前票数,咱们选用锁降级。

然后是咱们履行的代码

public class RestaurantTest {
​
  public static void main(String[] args) {
    Restaurant restaurant = new Restaurant();
​
    for (int i = 0; i < 10; ++i) {
      new Thread(restaurant::grabTickets, "食客" + (i + 1)).start();
      new Thread(restaurant::getNum, "吃瓜大众" + (i + 1)).start();
     }
​
   }
​
}

来看一下履行结果

用户1抢到了票,现在还剩余的票数为:5
吃瓜大众1检查了现在的票数:5
吃瓜大众2检查了现在的票数:5
用户2抢到了票,现在还剩余的票数为:4
用户3抢到了票,现在还剩余的票数为:3
吃瓜大众3检查了现在的票数:3
用户4抢到了票,现在还剩余的票数为:2
吃瓜大众6检查了现在的票数:2
吃瓜大众4检查了现在的票数:2
吃瓜大众5检查了现在的票数:2
用户7抢到了票,现在还剩余的票数为:1
用户6抢到了票,现在还剩余的票数为:0
用户5没票了
吃瓜大众9检查了现在的票数:0
吃瓜大众7检查了现在的票数:0
用户9没票了
用户8没票了
吃瓜大众8检查了现在的票数:0
用户10没票了
吃瓜大众10检查了现在的票数:0
​
进程已结束,退出代码0

这就是这篇文章的内容了,欢迎大家的讨论,如有讹夺,也请指出,谢谢~