Java读写锁ReentrantReadWriteLock之锁降级案例

之前了解读写锁的时候,只知道读读共享,读写互斥。

当线程先获取到写锁,然后再去获取读锁时,接着再释放写锁。这个过程叫做锁降级

如果当前线程先获取读锁,然后再去获取写锁,这个时候会出现死锁的情况。

这个时候小脑袋瓜就开始嗡嗡作响了。

  1. 锁为什么能够降级,却不能够升级?
  2. 在什么场景下会出现锁降级的情况?

问题1 : 锁为什么能够降级,却不能够升级?

首先读锁可以被多个线程持有,但是写锁同一时刻只能被一个线程拥有。
按照这个理论我们来举个例子:我们知道读写锁是互斥的,读的时候不能写

  • 那么假设我有10个线程获取了读锁,这个时候10个线程都得升级成写锁,这个时候问题就出来了,你读锁还没释放啊!你要获取的写锁只能在等待队列里面排队,等待着这10个线程释放读锁,但是这10个线程释放不了,因为在获取写锁的时候已经死锁了。

这里稍微小小的做个总结:

  • 读的时候不能够写,因为多线程情况下写锁会排在等待队列中读锁的后面,读都还没释放,就去获取写锁,就会造成死锁。
  • 但是写的时候能读,因为写锁只能被一个线程获取,

问题2 : 在什么场景下会出现锁降级的情况?

举个例子 :
假设一个事务执行要10秒,写操作占用1秒,其他的都是读操作9秒。
在多个线程争抢读写锁的情况下:
非降级情况

  • A线程拿到这个写锁就开始处理最后再释放,那么时间就是10秒,同时还可能阻塞着其他线程10秒。
  • A线程只在写的时候加上写锁,其他读的情况不加锁,那么可能会出现刚释放完写锁,另一个线程就开始改数据,但是A这边可能引起数据不一致。
  • A 先释放写锁,然后再获取读锁。可能就会出现别的线程抢到了写锁,A这边获取读锁的时候出现阻塞,打断了事务的进行。

锁降级的情况
当A获取完写锁并执行完1秒的写操作之后,降级成读锁,并释放写锁。
情况1:假设等待队列排在前面的是读锁线程,那么在A释放完写锁之后,等待队列中的读锁线程被传播唤醒,开始执行各自任务。
情况2:但是如果等待队列排在第一位的是写锁线程的话,不好意思还是在阻塞中。因为A还没释放完读锁,所以A还能继续执行下面操作,事务的连续性不会被打断。

当我们要保证事务的完整性,如果仅仅只用写锁去做,耗时太长,先释放写锁,再获取读锁,可能在获取读锁的时候,别的线程已经在抢写锁了,而你读锁只能等待!这个时候你的事务不仅被打断了,可能执行的时间更长。
而在锁降级的帮助下,情况1的优势发挥了出来。

总结

  • 在耗时长的事务中,锁降级的优势:
  1. 使写锁占用时间缩短.
  2. 线程并发的程度增高
  3. 并且保证了事务不被打断。

另外附上验证的代码块:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {
    private Logger logger = LoggerFactory.getLogger(ReadWriteLockTest.class);
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

	/**
	 *	这里仅仅是想知道锁重入的情况,是不是这个时候加入的锁会到等待队列里面排队。 
	 */
    public void queryData() {
        try {
            Thread.sleep(500);
            readLock.lock();
            logger.info("查询数据完成.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }

    }

    public void test3() throws Exception {
        // 开始锁降级
        writeLock.lock();
        logger.info("主线程抢到写锁...");
       	// 这里的休眠是为了让下面线程能在预想的情况下加入等待队列.
        Thread.sleep(500);
		// 这里就是假设等待队列里面排在前面的是读锁线程
        processReadLock(1); // 这里可以和下面processWriteLock对调
		
        processWriteLock(2);

        Thread.sleep(500);
		// 开始锁降级
        readLock.lock(); // A 降级开始
        // 锁降级完成
        writeLock.unlock();
        logger.info("主线程释放写锁");
        queryData();
        readLock.unlock(); // A 降级结束
        logger.info("主线程读锁释放");
        logger.info("过程结束..");
    }

    private void processWriteLock(int threadIndex) {
        new Thread(() -> {
            logger.info("线程" + threadIndex + " 写锁开始竞争,阻塞中.");
            writeLock.lock();
            logger.info("线程" + threadIndex + " 写锁执行中..");
            writeLock.unlock();
            logger.info("线程" + threadIndex + " 写锁释放..");
        }).start();
    }

    private void processReadLock(int threadIndex) {
        new Thread(() -> {
            logger.info("线程" + threadIndex + " 读锁开始竞争,阻塞中.");
            readLock.lock();
            logger.info("线程" + threadIndex + " 读锁执行中..");
            readLock.unlock();
            logger.info("线程" + threadIndex + " 读锁释放..");
        }).start();
    }


    public static void main(String[] args) throws Exception {
        ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest(); 
        readWriteLockTest.test3();
    }
}

打印结果:

主线程抢到写锁...
线程1 读锁开始竞争,阻塞中.  		// 这里等待队列中排在第一个的是读锁.
线程2 写锁开始竞争,阻塞中. 			// 队列中的第二个是写锁
主线程释放写锁					// 主线程先释放写锁,但是还持有着读锁
线程1 读锁执行中..				// 队列中的读锁已经被唤醒
线程1 读锁释放..					//读锁处理完毕.
查询数据完成.						// 主线程的读锁也处理完毕了
主线程读锁释放
过程结束..						// 主线程处理完毕
线程2 写锁执行中..				// 这个时候,等待队列中的写锁被触发
线程2 写锁释放..

我这里大概描述一下我想预测的几种情况:
主线程先抢到写锁,然后降级成为读锁。
锁降级成功之后:

  • 主线程此刻是处于读锁线程,那么等待队列中的读锁是否会被唤醒? // 答案是会被唤醒
  • 如果等待队列中排队的是写线程,在我主线程写锁先释放的时候,会不会抢到?// 答案是不会,因为主线程的读锁还没释放。

这里测试下来,就是在锁降级的情况下:

  • 如果等待队列中最前面的是写线程,必须等主线程的读写锁都释放的情况下,才会交给等待队列。
  • 如果等待队列中最前面的是读线程,那么主线程释放完读锁,就会被唤醒。

如果上述观点有误,请留言探讨,共同进步,谢谢观看。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Java 中,你可以使用 `java.util.concurrent.locks` 包中的 `ReadWriteLock` 接口来实现读写锁。下面是一个使用读写锁的示例代码: ```java import java.sql.*; import java.util.concurrent.locks.*; public class SQLiteTest { private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Lock readLock = rwLock.readLock(); private final Lock writeLock = rwLock.writeLock(); public void readFromDatabase() throws Exception { readLock.lock(); try { Class.forName("org.sqlite.JDBC"); Connection connection = DriverManager.getConnection("jdbc:sqlite:/path/to/your/database"); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT * FROM your_table"); // do something with the result set... } finally { readLock.unlock(); } } public void writeToDatabase() throws Exception { writeLock.lock(); try { Class.forName("org.sqlite.JDBC"); Connection connection = DriverManager.getConnection("jdbc:sqlite:/path/to/your/database"); Statement statement = connection.createStatement(); statement.executeUpdate("INSERT INTO your_table (column1, column2) VALUES ('value1', 'value2')"); } finally { writeLock.unlock(); } } } ``` 在这个示例代码中,我们使用 `ReentrantReadWriteLock` 类来创建读写锁。`readLock` 用于读操作,`writeLock` 用于写操作。在读操作中,我们首先获取读,然后执行查询操作。在写操作中,我们首先获取写,然后执行插入操作。 使用读写锁可以确保在同一时间只有一个线程可以进行写操作,而多个线程可以同时进行读操作。这可以提高程序的并发性能和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值