JUC之ReentrantReadWriteLock
1.背景
由于ReentrantLock是独占可重入锁,因此在进行操作的时候,不能够满足多线程同时操作数据,为了满足并发场景下的临界资源的数据共享,出现了ReentrantReadWriteLock共享锁。ReentrantReadWriteLock是ReadWriteLock接口的实现类,ReadWriteLock提供了readLock,writeLock两种方法。
2.共享锁
共享锁:即多个线程可以同时操作共享数据,但是存在这样的前置条件:读读共享,读写互斥,写读互斥,写写互斥。
小例子测试:
步骤一:先获取读写锁
private ReadWriteLock rw = new ReentrantReadWriteLock();
步骤二:创建读锁
private Lock r = rw.readLock();
步骤三:创建写锁
private Lock w = rw.writeLock();
步骤四:定义临界资源
private volatile int num = 0;
步骤五:创建读操作
public int getNum() {
r.lock();
System.out.println("当前线程获取读锁==" + Thread.currentThread().getName());
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return num;
} finally {
System.out.println("当前读取数据结束==" + Thread.currentThread().getName());
r.unlock();
}
步骤六:创建写操作
public void setNum() {
w.lock();
System.out.println("当前线程获取写锁==" + Thread.currentThread().getName());
try {
num++;
} finally {
System.out.println("当前线程释放写锁" + Thread.currentThread().getName());
w.unlock();
}
步骤七:编写main函数
public static void main(String[] args) {
ReentrantReadWriteLockDemo reentrantReadWriteLockDemo = new ReentrantReadWriteLockDemo();
new Thread(() -> {
reentrantReadWriteLockDemo.getNum();
}).start();
new Thread(() -> {
reentrantReadWriteLockDemo.getNum();
}).start();
new Thread(() -> {
reentrantReadWriteLockDemo.getNum();
}).start();
new Thread(() -> {
reentrantReadWriteLockDemo.getNum();
}).start();
}
测试:
1.读读共享
可以看到读读是共享的,也就是说可以同时获取共享资源。
2.读写互斥、写读互斥
更改某个线程,调用写操作
可以看到读写是互斥的。简而言之,当读的时候,写需要等待,当读释放锁,写才可以获取,当写释放才可以读。
3.写写互斥
获取和释放写锁串行执行。
3.锁降级
在可重入读写锁中存在锁降级的情况,也就是说如果当前线程持有写锁,在不释放写锁的情况下,还可以占据读锁。
测试:
增加一个共享变量flag,并完成锁降级。锁降级的顺序是:获取写锁->获取读锁->释放写锁->释放读锁。【一定要是这个过程,否则会出现脏读,也就是当线程A获取写锁执行完逻辑后,释放写锁,如果此时线程B获取写锁后,对数据完成修改,当线程A获取了线程B已经修改的数据,此时就出现了脏读】
package JUC;
import ThreadDemo.ThreadAl;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 测试读写锁的demo
*/
public class ReentrantReadWriteLockDemo {
private volatile boolean flag=false;
private volatile int num = 0;
//创建读写锁
private ReadWriteLock rw = new ReentrantReadWriteLock();
//获取读锁
private Lock r = rw.readLock();
//获取写锁
private Lock w = rw.writeLock();
public int getNum() {
r.lock();
System.out.println("当前线程获取读锁==" + Thread.currentThread().getName());
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return num;
} finally {
System.out.println("当前读取数据结束==" + Thread.currentThread().getName());
r.unlock();
}
}
public void setNum() {
w.lock();
System.out.println("当前线程获取写锁==" + Thread.currentThread().getName());
try {
num++;
r.lock();
System.out.println("写锁降级,获取读锁成功,Thread===="+Thread.currentThread().getName());
} finally {
System.out.println("当前线程释放写锁" + Thread.currentThread().getName());
w.unlock();
}
try{
if(flag){
System.out.println("【执行业务逻辑,写锁降级为读锁】");
}
}finally {
r.unlock();
System.out.println("写锁降级,释放读锁成功,Thread===="+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ReentrantReadWriteLockDemo reentrantReadWriteLockDemo = new ReentrantReadWriteLockDemo();
new Thread(() -> {
reentrantReadWriteLockDemo.getNum();
}).start();
new Thread(() -> {
reentrantReadWriteLockDemo.setNum();
}).start();
new Thread(() -> {
reentrantReadWriteLockDemo.flag = true;
}).start();
new Thread(() -> {
reentrantReadWriteLockDemo.getNum();
}).start();
}
}
运行结果: