读写锁基本概念
ReadWriteLock是Java并发包中的一个接口,它定义了两种锁:读锁(Read Lock)和写锁(Write Lock),真正的实现类是ReentrantReadWriteLock。读锁允许多个线程同时读取共享资源,而写锁则要求独占资源,即当一个线程持有写锁时,其他线程不能获取读锁或写锁。
读写锁的作用
在进行共享资源的并发访问时,不论是使用synchronized还是使用重入锁ReentrantLock,一次都只允许一个线程访问共享资源,但是很多时候我们只是要读取共享资源,并不修改共享资源,多个线程同时读取共享资源并不会产生不一致问题,而且还能提高并发效率,对共享资源修改时就只让一个线程进入,又保证数据的一致性,这就是读写锁ReadWriteLock诞生的原因。读写锁分离是一种常见的锁思想。例如在一些数据库内,利用读锁让多个事务可以同时读某行数据,并且数据不会被修改,利用写锁保证只有一个事务可以修改某行数据,并且不会被其它事务读取到脏数据。
写锁:也叫作排他锁,如果数据有加写锁,就只有持有写锁的线程才能对数据进行操作,数据加持着写锁时,其他线程不能加写锁,也不能施加读锁。
读锁:也叫作共享锁,多个线程可以对同一个数据添加多个读锁,数据被加上读锁后就不能再被加上写锁。
读写锁的使用
使用ReadWriteLock实现读写控制示例:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class RWLock {public static final CountDownLatch countDownLatch = new CountDownLatch(10);public static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();//获取读锁public static final ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();//获取写锁public static final ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();//8个线程模拟读操作for (int i = 0; i < 8; i++) {new Thread(() -> {readLock.lock();try {Thread.sleep(1000); // 模拟读操作countDownLatch.countDown();} catch (InterruptedException e) {e.printStackTrace();} finally {readLock.unlock();}}).start();}//2个线程模拟写操作for (int i = 0; i < 2; i++) {new Thread(() -> {writeLock.lock();try {Thread.sleep(2000); // 模拟写操作countDownLatch.countDown();} catch (InterruptedException e) {e.printStackTrace();} finally {writeLock.unlock();}}).start();}//等待所有操作完成countDownLatch.await();long end = System.currentTimeMillis();System.out.println("任务完成,耗时:" + (end - start));}
}
//任务完成,耗时:3081
使用ReentrantLock实现读写控制示例:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;public class RWLock {public static final CountDownLatch countDownLatch = new CountDownLatch(10);public static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();for (int i = 0; i < 10; i++) {new Thread(() -> {lock.lock();try {Thread.sleep(1000); // 模拟读操作countDownLatch.countDown();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}).start();}//等待所有操作完成countDownLatch.await();long end = System.currentTimeMillis();System.out.println("任务完成,耗时:" + (end - start));}
}
//任务完成,耗时:10155
以上两个示例中,使用ReadWriteLock实现的读写控制任务总耗时3秒左右,而使用ReentrantLock的实现的读写控制任务总耗时10秒左右,在这个例子中ReadWriteLock比ReentrantLock快了3倍多,这是否就意味着ReadWriteLock的性能就一定比ReentrantLock好呢?其实不是的,ReadWriteLock适用于读多写少的场景,如上面例子中使用了8个线程模拟读操作,2个线程模拟写操作就是读多写少的场景,在写操作密集的场景下ReadWriteLock的复杂性使它具有更大的锁开销。应该在不同的场景下选择合适的锁。