1. ReentrantReadWriteLock
1.1. 例子
package com.huangbei.test2;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.huangbei.util.Sleeper.sleep;
@Slf4j(topic = "c.Test-ReadWriteLock")
public class Test18 {
public static void main(String[] args) {
Data data = new Data();
//1.两个线程读
/*new Thread(() -> {
data.read();
}).start();
new Thread(() -> {
data.read();
}).start();*/
//2.两个线程一个读,一个写
/*new Thread(() -> {
data.write();
}).start();
new Thread(() -> {
data.read();
}).start();*/
//3.两个线程写
new Thread(() -> {
data.write();
}).start();
new Thread(() -> {
data.write();
}).start();
}
}
@Slf4j(topic = "c.Data")
class Data {
private int data;
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock r = readWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock w = readWriteLock.writeLock();
public int read() {
r.lock();
log.debug("获取到读锁");
try {
log.debug("读取数据...");
sleep(1);
log.debug("读取完毕,返回.");
return data;
} finally {
r.unlock();
}
}
public void write() {
w.lock();
log.debug("获取到写锁");
try {
log.debug("写入数据...");
sleep(1);
data = 0;
log.debug("写入完毕,返回.");
} finally {
w.unlock();
}
}
}
结果:
第1种情况:两个线程读,读读共享,两个线程能同时获得读锁。
18:17:45.335 [Thread-1] c.Data - 获取到读锁
18:17:45.341 [Thread-1] c.Data - 读取数据...
18:17:45.335 [Thread-0] c.Data - 获取到读锁
18:17:45.342 [Thread-0] c.Data - 读取数据...
18:17:46.346 [Thread-0] c.Data - 读取完毕,返回.
18:17:46.349 [Thread-1] c.Data - 读取完毕,返回.
第2种情况:一个线程读一个线程写,读写互斥,获取写锁必须等读锁释放,反之亦然。
18:19:48.308 [Thread-0] c.Data - 获取到写锁
18:19:48.313 [Thread-0] c.Data - 写入数据...
18:19:49.334 [Thread-0] c.Data - 写入完毕,返回.
18:19:49.334 [Thread-1] c.Data - 获取到读锁
18:19:49.334 [Thread-1] c.Data - 读取数据...
18:19:50.341 [Thread-1] c.Data - 读取完毕,返回.
第3种情况:两个线程写,写写互斥。
18:20:23.241 [Thread-0] c.Data - 获取到写锁
18:20:23.249 [Thread-0] c.Data - 写入数据...
18:20:24.264 [Thread-0] c.Data - 写入完毕,返回.
18:20:24.264 [Thread-1] c.Data - 获取到写锁
18:20:24.265 [Thread-1] c.Data - 写入数据...
18:20:25.279 [Thread-1] c.Data - 写入完毕,返回.
1.2. 继承关系
1.3. 可重入
读写锁是可重入的,但由于是两种锁,所以重入的顺序有限制。
锁重入是对于同一个线程而言的
- 获取读锁再获取写锁,这是不支持的。
- 获取写锁再获取读锁,这是支持的。
1.3.1. 例子
package com.huangbei.test2;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.huangbei.util.Sleeper.sleep;
@Slf4j(topic = "c.Test-ReadWriteLock")
public class Test20 {
public static void main(String[] args) {
Data2 data = new Data2();
//1. 先获取读锁,再获取写锁
/*
new Thread(() -> {
data.readAndWrite();
}).start();
*/
//2. 先获取写锁,再获取读锁
new Thread(() -> {
data.writeAndRead();
}).start();
}
}
@Slf4j(topic = "c.Data")
class Data2 {
private int data;
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock r = readWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock w = readWriteLock.writeLock();
public int readAndWrite() {
r.lock();
log.debug("获取到读锁");
try {
log.debug("读取数据...");
sleep(1);
log.debug("读取完毕.");
w.lock();
log.debug("获取到写锁");
try {
log.debug("我要写入数据.");
sleep(1);
} finally {
w.unlock();
}
return data;
} finally {
r.unlock();
}
}
public void writeAndRead() {
w.lock();
log.debug("获取到写锁");
try {
log.debug("写入数据...");
sleep(1);
data = 0;
log.debug("写入完毕.");
r.lock();
log.debug("获取到读锁");
try {
log.debug("我要读数据.");
sleep(1);
log.debug("读取完毕.");
} finally {
r.unlock();
}
} finally {
w.unlock();
}
}
}
1.3.2. 结果
//1. 先获取读锁,再获取写锁,写锁lock会被阻塞
21:25:07.560 [Thread-0] c.Data - 获取到读锁
21:25:07.565 [Thread-0] c.Data - 读取数据...
21:25:08.566 [Thread-0] c.Data - 读取完毕.
//2. 先获取写锁,再获取读锁,可以。
21:26:22.759 [Thread-0] c.Data - 获取到写锁
21:26:22.763 [Thread-0] c.Data - 写入数据...
21:26:23.768 [Thread-0] c.Data - 写入完毕.
21:26:23.768 [Thread-0] c.Data - 获取到读锁
21:26:23.769 [Thread-0] c.Data - 我要读数据.
21:26:24.770 [Thread-0] c.Data - 读取完毕.
Process finished with exit code 0
1.4. 原理
读写锁本质是是使用的Sync对象,它里面有读锁和写锁的上锁和释放方法。 具体原理见ReentrantReadWriteLock源码。
