读写锁与AQS
ReadWriteLock
概念
维护一对关系锁,一个只能用于读操作,一个只能用于写操作
读锁可以由多个读线程同时持有,写锁是排他的。
**同一时间两把锁不能被不同线程持有**
public class ReadWriteLock_Demo {
//不希望读写的内容都不一样,需要加入锁机制
volatile long i =0;
Lock lock = new ReentrantLock();
ReadWriteLock rwLock = new ReentrantReadWriteLock();
public long read() {
try {
rwLock.readLock().lock();
return i;
} finally {
rwLock.readLock().unlock();
}
}
public void write() {
rwLock.writeLock().lock();
i++;
rwLock.writeLock().unlock();
}
// public void read() {
// lock.lock();
//
// long a = i;
//
// lock.unlock();
// }
//
// public void write() {
// lock.lock();
//
// i++;
//
// lock.unlock();
// }
public static void main(String[] args) throws InterruptedException {
final ReadWriteLock_Demo demo = new ReadWriteLock_Demo();
for (int i=0;i<=30; i++){
int n = i;
new Thread(new Runnable() {
@Override
public void run() {
for (int j=0; j<80000; j++){
if (n%3==0) demo.write();
else demo.read();
}
}
}).start();
}
Thread.sleep(10000);
System.out.println(demo.read());
}
}
适用场景
适合读取操作多于写入操作的场景,改进互斥锁的性能,
比如:集合的并发线程安全性改造、缓存组件
public class HashMap_Demo {
private final Map<String,Object> map = new HashMap<>();
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock r = readWriteLock.readLock();
private final Lock w = readWriteLock.writeLock();
public Object get(String key){
r.lock();
try {
return map.get(key);
}finally {
r.unlock();
}
}
public Object put(String key,Object obj){
w.lock();
try {
return map.put(key,obj);
}finally {
w.unlock();
}
}
public Object[] allkeys(){
r.lock();
try {
return map.keySet().toArray();
}finally {
r.unlock();
}
}
public void clear(){
w.lock();
try {
map.clear();
}finally {
w.unlock();
}
}
}
锁降级
指的是写锁降级成为读锁,持有写锁的同时,在获取读锁,随后释放写锁的过程
写锁是线程独占,读锁是共享,所以写->读是降级.(读->写,是不能实现的)
实现最简单的读写锁
public class KaneReadWriteLock {
volatile AtomicInteger readCount = new AtomicInteger(0);
AtomicInteger writeCount = new AtomicInteger(0);
//独占锁 拥有者
AtomicReference<Thread> owner = new AtomicReference<>();
//等待队列
public volatile LinkedBlockingQueue<WaitNode> waiters = new LinkedBlockingQueue<WaitNode>();
class WaitNode {
int type = 0; //0 为想获取独占锁的线程, 1为想获取共享锁的线程
Thread thread = null;
int arg = 0;
public WaitNode(Thread thread, int type, int arg) {
this.thread = thread;
this.type = type;
this.arg = arg;
}
}
//获取独占锁
public void lock() {
int arg = 1;
//尝试获取独占锁,若成功,退出方法, 若失败...
if (!tryLock(arg)) {
//标记为独占锁
WaitNode waitNode = new WaitNode(Thread.currentThread(), 0, arg);
waiters.offer(waitNode); //进入等待队列
//循环尝试拿锁
for (; ; ) {
//若队列头部是当前线程
WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread()) {
if (!tryLock(arg)) { //再次尝试获取 独占锁
LockSupport.park(); //若失败,挂起线程
} else { //若成功获取
waiters.poll(); // 将当前线程从队列头部移除
return; //并退出方法
}
} else { //若不是队列头部元素
LockSupport.park(); //将当前线程挂起
}
}
}
}
//释放独占锁
public boolean unlock() {
int arg = 1;
//尝试释放独占锁 若失败返回true,若失败...
if (tryUnlock(arg)) {
WaitNode next = waiters.peek(); //取出队列头部的元素
if (next != null) {
Thread th = next.thread;
LockSupport.unpark(th); //唤醒队列头部的线程
}
return true; //返回true
}
return false;
}
//尝试获取独占锁
public boolean tryLock(int acquires) {
//如果read count !=0 返回false
if (readCount.get() != 0)
return false;
int wct = writeCount.get(); //拿到 独占锁 当前状态
if (wct == 0) {
if (writeCount.compareAndSet(wct, wct + acquires)) { //通过修改state来抢锁
owner.set(Thread.currentThread()); // 抢到锁后,直接修改owner为当前线程
return true;
}
} else if (owner.get() == Thread.currentThread()) {
writeCount.set(wct + acquires); //修改count值
return true;
}
return false;
}
//尝试释放独占锁
public boolean tryUnlock(int releases) {
//若当前线程没有 持有独占锁
if (owner.get() != Thread.currentThread()) {
throw new IllegalMonitorStateException(); //抛IllegalMonitorStateException
}
int wc = writeCount.get();
int nextc = wc - releases; //计算 独占锁剩余占用
writeCount.set(nextc); //不管是否完全释放,都更新count值
if (nextc == 0) { //是否完全释放
owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
//获取共享锁
public void lockShared() {
int arg = 1;
if (tryLockShared(arg) < 0) { //如果tryAcquireShare失败
//将当前进程放入队列
WaitNode node = new WaitNode(Thread.currentThread(), 1, arg);
waiters.offer(node); //加入队列
for (; ; ) {
//若队列头部的元素是当前线程
WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread()) {
if (tryLockShared(arg) >= 0) { //尝试获取共享锁, 若成功
waiters.poll(); //将当前线程从队列中移除
WaitNode next = waiters.peek();
if (next != null && next.type == 1) { //如果下一个线程也是等待共享锁
LockSupport.unpark(next.thread); //将其唤醒
}
return; //退出方法
} else { //若尝试失败
LockSupport.park(); //挂起线程
}
} else { //若不是头部元素
LockSupport.park();
}
}
}
}
//解锁共享锁
public boolean unLockShared() {
int arg = 1;
if (tryUnLockShared(arg)) { //当read count变为0,才叫release share成功
WaitNode next = waiters.peek();
if (next != null) {
LockSupport.unpark(next.thread);
}
return true;
}
return false;
}
//尝试获取共享锁
public int tryLockShared(int acquires) {
for (; ; ) {
if (writeCount.get() != 0 &&
owner.get() != Thread.currentThread())
return -1;
int rct = readCount.get();
if (readCount.compareAndSet(rct, rct + acquires)) {
return 1;
}
}
}
//尝试解锁共享锁
public boolean tryUnLockShared(int releases) {
for (; ; ) {
int rc = readCount.get();
int nextc = rc - releases;
if (readCount.compareAndSet(rc, nextc)) {
return nextc == 0;
}
}
}
}
模板方法模式
由于ReentrntLock和ReadWriteLock中存在大量的重复代码需要进行优化.
----此处有大量的代码后期补充上传到gitee
AQS抽象队列同步器-
AQS: ReentryLock, ReadWriteLock等有很多公共的部分, 被抽取出来放到AQS, 一些独特的地方由子类自己去实现
AbstractQueuedSynchronizer 是jdk提供的一个类
https://zhuanlan.zhihu.com/p/178991617
上面我们自己实现的AQS里面由两个count值, 一个readCount, 一个是writeCount, 但在jdk的AQS中只有一个state字段用来存储这两个count的值.
ReadWriteLock用一个int存储了两个count值
int =4byte=32 位 前16位为readCount 后16位 为writeCount