闲聊
大学的时候是有粗略的学了ReentrantReadWriteLock,大概记了下相关知识点(都是为了应付面试),实际没用过几次。
工作之后,我们的项目中很少用到,所以基本上相关的知识都忘了。
直到有天老同学问我ReentrantReadWriteLock相关的问题。他写了一段代码,用到了ReentrantReadWriteLock,但是一直获取不到读锁,让我帮忙看看。我就带着疑问重新学习了下。我觉得这样学习的效率挺高,而且印象深刻。
ReentrantReadWriteLock
意思是可重入读写锁。包含了两个意思,可重入,可获得读锁和写锁。
读写锁
多个线程读取共享数据(比如数据库)时,需要使用锁来确保数据的读取和写入的正确性,所以需要加锁。
很简单的道理就是,我在读取数据的时候你不能写;我在写的时候你不能读取也不能写;读写互斥,写写互斥;没有人在写的时候,大家都可以读取数据,读读不互斥。
读锁
没有线程在写的时候,大家都可以读取数据。
即没有写锁的情况下,多个线程都可以获取读锁,所以读锁是共享锁。
线程在读取数据时获取读锁意思就是告诉其他线程【我正在读取数据,你等下再写入吧】。
线程读取完数据后释放读锁。
public class MyThread implements Runnable{
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
@Override
public void run() {
readLock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "开始读取");
// 模拟读取操作
readLock.unlock();
System.out.println("线程" + Thread.currentThread().getName() + "读取完毕");
}
}
线程Thread-0开始读取
线程Thread-0读取完毕
public class MyThread implements Runnable{
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
@Override
public void run() {
readLock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "开始读取");
// 模拟读取操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
readLock.unlock();
System.out.println("线程" + Thread.currentThread().getName() + "读取完毕");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread threadA = new Thread(myThread);
threadA.start();
Thread threadB = new Thread(myThread);
threadB.start();
}
}
线程Thread-0开始读取
线程Thread-1开始读取
线程Thread-0读取完毕
线程Thread-1读取完毕
写锁
我想要写入某个数据,要先确保这个数据没有人在读,也没有人在写,我才能去写数据。
即当其他线程没有读锁和写锁的时候,才能获取写锁。写锁是互斥锁。
线程写入完数据后,释放写锁。
public class MyThread implements Runnable{
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
@Override
public void run() {
writeLock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "开始写入");
try {
System.out.println(Thread.currentThread().getName() + " 写入数据");
} finally {
writeLock.unlock();
System.out.println("线程" + Thread.currentThread().getName() + "结束写入");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread threadA = new Thread(myThread);
threadA.start();
}
}
线程Thread-0开始写入
Thread-0 写入数据
线程Thread-0结束写入
public class MyThread implements Runnable{
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
@Override
public void run() {
writeLock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "开始写入");
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 写入数据");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
writeLock.unlock();
System.out.println("线程" + Thread.currentThread().getName() + "结束写入");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread threadA = new Thread(myThread);
threadA.start();
Thread threadB = new Thread(myThread);
threadB.start();
}
}
线程Thread-1开始写入
Thread-1 写入数据
线程Thread-1结束写入
线程Thread-0开始写入
Thread-0 写入数据
线程Thread-0结束写入
可重入
即可重复获取锁。比如:
当前线程已经获取了读锁,它可以再获取一次读锁;
public class MyThread implements Runnable{
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
@Override
public void run() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "得到了读锁");
readLock.lock();
System.out.println(Thread.currentThread().getName() + "又得到了读锁");
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了一个读锁");
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了最后一个读锁");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread threadA = new Thread(myThread);
threadA.start();
}
}
Thread-0得到了读锁
Thread-0又得到了读锁
Thread-0释放了一个读锁
Thread-0释放了最后一个读锁
当前线程已经获取了写锁,它可以再获取一次写锁。
public class MyThread implements Runnable{
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
@Override
public void run() {
writeLock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "得到了写锁");
writeLock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "又得到了写锁");
writeLock.unlock();
System.out.println("线程" + Thread.currentThread().getName() + "释放了写锁");
writeLock.unlock();
System.out.println("线程" + Thread.currentThread().getName() + "再次释放了写锁");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread threadA = new Thread(myThread);
threadA.start();
}
}
线程Thread-0得到了写锁
线程Thread-0又得到了写锁
线程Thread-0释放了写锁
线程Thread-0再次释放了写锁
锁降级
锁降级的意思:该线程在获取了写锁之后,写锁未释放之前,可以获取读锁。
这是让线程写完之后能读取到正确的数据。保证数据的可读性,不会读到脏数据。
public class MyThread implements Runnable{
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
@Override
public void run() {
writeLock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "得到了写锁");
readLock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "得到了读锁");
writeLock.unlock();
System.out.println("线程" + Thread.currentThread().getName() + "释放了写锁");
readLock.unlock();
System.out.println("线程" + Thread.currentThread().getName() + "释放了读锁");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread threadA = new Thread(myThread);
threadA.start();
}
}
线程Thread-0得到了写锁
线程Thread-0得到了读锁
线程Thread-0释放了写锁
线程Thread-0释放了读锁
在获取写锁之后,写入数据,再获取读锁,这时写锁还是在的。
需要先把写锁释放了,然后再读取操作完成之后再释放读锁。
总结
读写锁互斥,读锁与读锁不互斥,写锁与写锁互斥。
对于同一个线程,读写锁是可以重复获取的。第二次获取相同的锁时,第一个锁还是存在的,需要释放。
锁降级(在获取写锁后可以再去获得读锁):在获取读锁之后,要优先释放写锁。