1. 基础知识
- 允许同一时刻多个线程的访问;是共享锁的代表锁。同一时刻多个读的线程访问,但是在写线程访问时,所有的读线程和其它写线程还是会被阻塞。
- 适合的场景是多读少写。
- 也是重入锁。
2. 实现分析
- 读写状态的设计:因为需要使用AQS的状态保存多个读线程和一个写线程的状态;而需要使用一个整型变量维护多种状态,就需要“按位切割使用”这个变量,高16位表示读,低16位表示写。
- 锁降级:
- 指的是写锁降级为读锁。过程是:持有写锁,再获取读锁,再释放写锁的过程。
- 获取写锁之后,再获取读锁,是否有必要? 是必须的。主要为了保证数据的可见性。若是当前线程不获取读锁,而是直接释放写锁,假设另外一个线程T获取了写锁并修改了数据,那么当前线程无法感知到线程T的数据更新。如果当前线程获取了读锁,即遵循锁降级的步骤,则线程T将会被阻塞,知道当前线程使用完数据并释放了读锁之后,线程T才能获取写锁进行数据更新。
- 不支持锁升级(把持读锁,获取写锁,最后是否读锁的过程)。目的也是保证数据可见性,若读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了数据,则其更新对其他获取到读锁的线程是不可见的。
3. 读写锁操作
- 读写锁:对于只读的时候, 不需要加锁,但是, 只要和写混合, 或者是单纯的写都需要加锁. 防止数据的错误.
public class ReadWriteLockDemo1 {
private static final ReadWriteLock lock = new ReentrantReadWriteLock();
private static final Lock readLock = lock.readLock();
private static final Lock writeLock = lock.writeLock();
public static void main(String[] args) throws InterruptedException {
IntStream.rangeClosed(0, 5).forEach(item -> {
new Thread(ReadWriteLockDemo1::write).start();
});
TimeUnit.SECONDS.sleep(2);
IntStream.rangeClosed(0, 5).forEach(item -> {
new Thread(ReadWriteLockDemo1::read).start();
});
}
private static void write() {
try {
System.out.println(Thread.currentThread().getName() + "write in");
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "write work");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
System.out.println(Thread.currentThread().getName() + "write end");
}
private static void read() {
try {
System.out.println(Thread.currentThread().getName() + "read in");
readLock.lock();
System.out.println(Thread.currentThread().getName() + "read work");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
System.out.println(Thread.currentThread().getName() + "read end");
}
}