在上一篇文章中我们手写了独占锁ReentrantLock,但实际在我们的开发中为了达到读写分离提高性能,也可能会使用到读写锁也就是我们的ReentrantReadWriteLock,但有同学就会问了我知道写锁是要加锁的但读锁为什么也要加锁呢?其实很简单因为如果读不加锁我们读到的数据很有可能就是被写锁修改过的数据了,这个有点类似于我们数据库中并发问题的不可重复读,就是说在一个事物中读取到的结果不一致,原因就是前后两次的读取过程中被别的事物将数据修改了,所以施加读锁也是很有必要的。
JDK读写锁特性
如果我们要自己实现一把读写锁,我们就必须对读写锁有一定的认知:
- 读锁与写锁是互斥的,写锁与写锁是互斥的,但读锁与读锁是共享的。简单来说就是读锁是共享锁,写锁是独占锁也称之为互斥锁
- 读写锁中的写锁与我们的reentrantLock一样是独占锁,同样也是可重入锁,所以读写锁中的写锁具备了reentrantLock一样的特性,可重入并且重入得次数必须等于解锁次数才能解锁(重入次数<解锁次数则抛异常)
读写锁中读锁为共享锁,也就是多个线程可以共同读取同一条数据,但它同样是可重入得,也就是当读锁重入次数!=读锁解锁次数 一样会出现独占锁一样的情况就是读锁并不会释放。
public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
new Thread(() -> {
lock.readLock().lock();
System.out.println("读锁开始工作");
LockSupport.parkNanos(1000000000*3L);
System.out.println("读锁结束工作");
lock.readLock().lock();
lock.readLock().unlock();
}).start();
new Thread(() -> {
lock.writeLock().lock();
System.out.println("写锁开始工作");
LockSupport.parkNanos(1000000000*3L);
System.out.println("写锁结束工作");
lock.writeLock().unlock();
}).start();
}
读锁开始工作
读锁结束工作
读写锁中的读锁解锁次数和其内部readCount有一定关系,就是必须是每一次解锁readCount-1,直至readCount=0我们才能称读锁被释放,简答来说就是众多读线程共同持有一把锁,只有每个线程执行完(释放锁)我们才能让下一个队列中的线程来抢锁。
手写读写锁构思
我们可以参照上一篇文章的手写reentrantLock来想想一下,我们是通过阻塞队列来装在挂起的线程,然后解锁时去unpark队列头部的线程,但是我们读写锁是要做到兼具共享与独占二者的特性,而我们通过reentrantLock事实上已经实现了独占锁,所以我们的难点就是如何在内部在实现一次共享锁就可以了。但是如何实现共享锁呢?
我们参照jdk读写锁,我们可以写不同的方法来表明这是读锁的加锁解锁方法,所以会有tryLockShared()、lockShared()、tryUnlockShared()、unlockShared()四个方法来表明这是读锁。如果我们把线程区分为读线程和写线程,假设某个线程抢到了锁,则对应的readCount/writeCount 也会去+1。如何区分队列中的线程是读线程还是写线程?
假设是我们写线程拿到了锁它结束后又该去唤醒下一个线程,那下一个线程是什么类型的线程呢?所以我们会封装一个内部类waitNode(源码是双向链表),然后每次lockShared抢锁时则会以waitNode的形式加入到阻塞队列以此来表示它属于读线程还是写线程。
怎么一次唤醒多个读线程
假设我们首先拿到的是写线程,当它执行完后它要去唤醒队列中的下一个线程,但此时假设队列中存在三个线程依次是 读线程1、读线程2、写线程,但如果我们只唤醒一个线程这与我们的独占锁是没有区别的,所以必须是去唤醒两个读线程,所以我们需要在lockShared()方法里去唤醒队列中下一个读线程,也就是说其中一个读线程拿到读锁了,则会去唤醒下一个队列头部的读线程(如果头部不是则不会去唤醒),以此来达到循环唤醒的目的。
手写读写锁代码
```java /**
- @author heian
- @create 2020-03-06-10:03 上午
- @description 读写锁
- 当共享锁占锁成功,独占锁无法抢锁成功;共享锁可以被多个线程占锁
- 当独占锁占锁成功,共享锁无法占锁成功;独占锁无法被多个线程占锁
- 不包含锁降级 */ public class MyReadWriteLock {
//想获取锁的线程 private LinkedBlockingQueue
queue = new LinkedBlockingQueue<>(); //被引用的线程 private AtomicReference reference = new AtomicReference<>(); //计算重入得次数 private AtomicInteger readCount = new AtomicInteger(0);//共享锁 private AtomicInteger writeCount = new AtomicInteger(0);//独占锁 //为了区分读线程还是写线程 class WaitNode{
int type = 0; //0:独占锁的线程 1:共享锁的线程
Thread thread = null;
public WaitNode(Thread thread, int type){
this.thread = thread;
this.type = type;
}
} /**
- 尝试获取锁(不是真的去拿锁,所以不用加入到阻塞队列)
- 修改count 和 reference acquire:重入的次数默认是1
*/
public boolean tryLock(int acquires){
if (readCount.get() != 0){
} if (writeCount.get() != 0){return false;
}else {//锁被占用(可能是自己)
if (Thread.currentThread() == reference.get()){
writeCount.set(writeCount.get()+acquires);//单线程 无需CAS
}
} return false; }//非公平的实现
if (writeCount.compareAndSet(writeCount.get(),writeCount.get()+1)){
reference.set(Thread.currentThread());
return true;
}
/**
- 抢锁(存在非公平现象)
修改 queue */ public void lock(){ //锁被占用,则CAS自旋不断地去抢锁 int arg = 1; if (!tryLock(arg)){
WaitNode readNode = new WaitNode(Thread.currentThread(),0);
queue.offer(readNode);
//lock 是不死不休所以得用for循环,既然CAS拿不到则由轻量级锁转为重量级锁(挂起阻塞)再一次去拿锁
for (;;){
WaitNode peek = queue.peek();
//队列可能一个线程,所以offer进来的或者说唤醒进来的,都会去判断是不是头部,是头部则再一次去抢锁
if (peek != null && peek.thread == Thread.currentThread()){
if (tryLock(arg)){
queue.poll();
break;
}else {
//可能一进来就是头部线程或者唤醒了非头部线程,挂起
LockSupport.park();
}
}else {
//不是头部线程,在队列并挂起
LockSupport.park();
}
}
} }
/**
- 释放锁 返回布尔类型是为了仿照jdk实现
- 修改 queue
*/
public boolean unlock(){
int arg = 1;
if (tryUnlock(arg)){
} return false; }WaitNode peek = queue.peek();
//存在队列为空可能,比如就一个抢锁的不会去加入到阻塞队列
if (peek != null){
LockSupport.unpark(peek.thread);
}
return true;
/**
- 尝试去解锁
- 解锁不用判断读锁占用情况:,修改count 和 reference
*/
public boolean tryUnlock(int releases){
if (reference.get() != Thread.currentThread()){
}else {throw new IllegalMonitorStateException("未能获取到锁,无法释放锁");
} }//只有是拿到锁的线程才有解锁的资格,所以此处是单线程
int value = writeCount.get()- releases;
writeCount.set(value);
//当你lock多次,但是unlock一次,此时是不会释放锁,只是不阻塞罢了
if (value == 0){
reference.set(null);
return true;
}else {
return false;
}
//读锁:独占锁 tryAcquireShared(AQS中的模板方法) public void lockShared(){
int arg = 1;
//>0 抢锁成功 <0 失败
if (tryLockShared(arg) <0){
WaitNode writeNode = new WaitNode(Thread.currentThread(),1);
queue.offer(writeNode);
for (;;){
WaitNode headNode = queue.peek();
if (headNode != null && headNode.thread == Thread.currentThread()){
//如果是头部线程,则再去抢锁,抢到了则移除,没抢到则挂起
if (tryLockShared(arg)>0){
queue.poll();
//移除后再去拿下一个,如果下一个也是读锁,则将其唤醒
WaitNode peek = queue.peek();
if (peek != null && peek.type == 1){
System.out.println("读锁被唤醒或读锁唤醒下一个线程");
LockSupport.unpark(peek.thread);//唤醒下一个线程,下一个线程是挂起1、挂起2
}
break;
}else {
LockSupport.park();//队列头部 挂起1
}
}else {
LockSupport.park();//非队列头部 挂起2
}
}
}
}
/**
- acquireShared()
- 尝试获取读锁
- 只修改count 因为reference是被多个线程引用
- 这里返回int 是为了模拟jdk源码
*/
public int tryLockShared(int acquires){
for (;;){
} }if (writeCount.get() != 0 ){//&& Thread.currentThread() != reference.get()
return -1;
}else {
int readNum = readCount.get();
//多个读线程 CAS操作改变,可能会失败所以需要自旋for
if (readCount.compareAndSet(readNum,readNum + acquires)){
return 1;
}
}
/**
- tryReleaseShared 这里返回布尔类型是为了仿照jdk实现
- 解除共享锁 意味着所有的读锁都释放了
*/
public boolean unlockShared(){
int arg = 1;
if (tryUnlockShared(arg)){
} return false; }//读锁释放了,则去判断队列中有无写锁
WaitNode peek = queue.peek();
if (peek != null){
System.out.println("读锁唤醒下一个线程");
LockSupport.unpark(peek.thread);//读锁执行完去唤醒下一个线程 一定是写锁
}
return true;
/**
- tryReleaseShared
- 尝试解开读锁,就直接修改readCount值即可
- 解锁不用判断写锁占用情况:读锁重入多次,解锁1次,则count就!=0,只有当读锁的count=0,才能表明读锁释放锁成功
*/
public boolean tryUnlockShared(int releases){
//这里无需去唤醒读锁,因为这里你解锁也只能唤醒一个,而是应该放在lockShared方法里,全部唤醒处在头部的读锁
for (;;){
} }int readNum = readCount.get();
int readNum2 = readNum - releases;
if (readCount.compareAndSet(readNum,readNum2)){
return readNum2 == 0;
}
}
测试准确性代码
```java
public static void main(String[] args) {
MyReadWriteLock lock = new MyReadWriteLock();
new Thread(() -> {
lock.lock();
System.out.println("写锁开始工作");
LockSupport.parkNanos(1000000000*3L);
System.out.println("写锁结束工作");
lock.unlock();
}).start();
new Thread(() -> {
lock.lock();
System.out.println("写锁开始工作");
LockSupport.parkNanos(1000000000*3L);
System.out.println("写锁结束工作");
lock.unlock();
}).start();
new Thread(() -> {
lock.lockShared();
System.out.println("读锁开始工作");
LockSupport.parkNanos(1000000000*3L);
System.out.println("读锁结束工作");
lock.unlockShared();
}).start();
new Thread(() -> {
lock.lockShared();
System.out.println("读锁开始工作");
LockSupport.parkNanos(1000000000*3L);
System.out.println("读锁结束工作");
lock.unlockShared();
}).start();
}
写锁开始工作
写锁结束工作
写锁开始工作
写锁结束工作
读锁被唤醒或读锁唤醒下一个线程
读锁开始工作
读锁开始工作
读锁结束工作
读锁结束工作
ok,至此完成手写读写锁的代码演示。但这个与我们今天要讲的AQS有什么关系呢?
引入AQS
AQS:AbstractQuenedSynchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制。**AQS说白了就是**一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。这个队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。用大白话来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物
看了上面的简单介绍是不是发现跟我们上述实现的基本一致,只是我这里没使用双向队列而是阻塞队列,还有就是它里面的这个state一个变量实际上它是以二进制的方式存储了两个count值,也就是上面我们的readCount和readCount。另外AQS是抽象类,它帮我们实现了锁的分配机制,但由于我们的抢锁或者释放锁的机制各有差异,所以它采用模板设计模式帮我们实现了主要的四个方法:
//方法一:类比我们的独占锁的lock(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//方法二:类比我们的独占锁的unlock(int arg)
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//方法三:类比我们的独占锁的lockShared(int arg)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//方法四:类比我们的独占锁的unlockShared(int arg)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
可以发现上面四个方法很类似,但是它的tryAcquire(arg)、tryRelease(arg)、tryAcquireShared(arg)、tryReleaseShared(arg)都是留给开发者自己去实现的。这也就是模板设计模式,帮我们制定好了实现的框架,你只要往里面填充自己的实现方式即可。比如我要画一个ppt,这个ppt的开头已经固定好了使我们公司的logo和价值观,但是里面的内容就得有我们用户自己去实现。那我们就利用上面的读写锁来抽象出来一个我们的AQS。
//方法一:类比我们的独占锁的trylock(int arg)
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
//方法二:类比我们的独占锁的tryUnlock(int releases)
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
//方法三:类比我们的独占锁的tryLockShared(int arg)
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
//方法四:类比我们的独占锁的tryUnlockShared(int releases)
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
AQS实现公平锁与非公平锁
第一步:抽象出自己的AQS类
/**
* @author heian
* @create 2020-03-05-8:21 下午
* @description
*/
public class MyAQS {
//想获取锁的线程
protected LinkedBlockingQueue<WaitNode> queue = new LinkedBlockingQueue<>();
//被引用的线程
protected AtomicReference<Thread> reference = new AtomicReference<>();
//计算重入得次数
protected AtomicInteger readCount = new AtomicInteger(0);//共享锁
protected AtomicInteger writeCount = new AtomicInteger(0);//独占锁
//为了区分读线程还是写线程
class WaitNode{
int type = 0; //0:独占锁的线程 1:共享锁的线程
Thread thread = null;
public WaitNode(Thread thread, int type){
this.thread = thread;
this.type = type;
}
}
/**
* 抢锁(存在非公平现象)
* 修改 queue
*/
public void lock(){
//锁被占用,则CAS自旋不断地去抢锁
int arg = 1;
if (!tryLock(arg)){
WaitNode readNode = new WaitNode(Thread.currentThread(),0);
queue.offer(readNode);
//lock 是不死不休所以得用for循环,既然CAS拿不到则由轻量级锁转为重量级锁(挂起阻塞)再一次去拿锁
for (;;){
WaitNode peek = queue.peek();
//队列可能一个线程,所以offer进来的或者说唤醒进来的,都会去判断是不是头部,是头部则再一次去抢锁
if (peek != null && peek.thread == Thread.currentThread()){
if (tryLock(arg)){
queue.poll();
break;
}else {
//可能一进来就是头部线程或者唤醒了非头部线程,挂起
LockSupport.park();
}
}else {
//不是头部线程,在队列并挂起
LockSupport.park();
}
}
}
}
/**
* 释放锁 返回布尔类型是为了仿照jdk实现
* 修改 queue
*/
public boolean unlock(){
int arg = 1;
if (tryUnlock(arg)){
WaitNode peek = queue.peek();
//存在队列为空可能,比如就一个抢锁的不会去加入到阻塞队列
if (peek != null){
LockSupport.unpark(peek.thread);
}
return true;
}
return false;
}
//读锁:独占锁 tryAcquireShared(AQS中的模板方法)
public void lockShared(){
int arg = 1;
//>0 抢锁成功 <0 失败
if (tryLockShared(arg) <0){
WaitNode writeNode = new WaitNode(Thread.currentThread(),1);
queue.offer(writeNode);
for (;;){
WaitNode headNode = queue.peek();
if (headNode != null && headNode.thread == Thread.currentThread()){
//如果是头部线程,则再去抢锁,抢到了则移除,没抢到则挂起
if (tryLockShared(arg)>0){
queue.poll();
//移除后再去拿下一个,如果下一个也是读锁,则将其唤醒
WaitNode peek = queue.peek();
if (peek != null && peek.type == 1){
System.out.println("读锁被唤醒或读锁唤醒下一个线程");
LockSupport.unpark(peek.thread);//唤醒下一个线程,下一个线程是挂起1、挂起2
}
break;
}else {
LockSupport.park();//队列头部 挂起1
}
}else {
LockSupport.park();//非队列头部 挂起2
}
}
}
}
/**
* tryReleaseShared 这里返回布尔类型是为了仿照jdk实现
* 解除共享锁 意味着所有的读锁都释放了
*/
public boolean unlockShared(){
int arg = 1;
if (tryUnlockShared(arg)){
//读锁释放了,则去判断队列中有无写锁
WaitNode peek = queue.peek();
if (peek != null){
System.out.println("读锁唤醒下一个线程");
LockSupport.unpark(peek.thread);//读锁执行完去唤醒下一个线程 一定是写锁
}
return true;
}
return false;
}
/**
* 尝试获取锁(不是真的去拿锁,所以不用加入到阻塞队列)
* 修改count 和 reference acquire:重入的次数默认是1
*/
public boolean tryLock(int acquires){
throw new UnsupportedOperationException();
}
/**
* 尝试去解锁
* 解锁不用判断读锁占用情况:,修改count 和 reference
*/
public boolean tryUnlock(int releases){
throw new UnsupportedOperationException();
}
/**
* acquireShared()
* 尝试获取读锁
* 只修改count 因为reference是被多个线程引用
* 这里返回int 是为了模拟jdk源码
*/
public int tryLockShared(int acquires){
throw new UnsupportedOperationException();
}
/**
* tryReleaseShared
* 尝试解开读锁,就直接修改readCount值即可
* 解锁不用判断写锁占用情况:读锁重入多次,解锁1次,则count就!=0,只有当读锁的count=0,才能表明读锁释放锁成功
*/
public boolean tryUnlockShared(int releases){
throw new UnsupportedOperationException();
}
}
第二步:实现自的ReentrantLock
/**
* @author heian
* @create 2020-03-07-4:18 下午
* @description 实现自己的AQS
* 实现公平锁与非公平锁
*/
public class MyReentrantLock {
private boolean isFair;
private Sync sync;
public MyReentrantLock(boolean isFair) {
this.isFair = isFair;
this.sync = new Sync();
}
public MyReentrantLock() {
this.sync = new Sync();
}
//采取源码方式 内部类继承
class Sync extends MyAQS{
@Override
public boolean tryLock(int acquires){
return isFair == true ? tryFairLock(acquires):tryNonFairLock(acquires);
};
//非公平锁实现方式
public boolean tryNonFairLock(int acquires){
if (readCount.get() != 0){
return false;
}
if (writeCount.get() != 0){
//锁被占用(可能是自己)
if (Thread.currentThread() == reference.get()){
writeCount.set(writeCount.get()+acquires);//单线程 无需CAS
}
}else {
//非公平的实现
if (writeCount.compareAndSet(writeCount.get(),writeCount.get()+1)){
reference.set(Thread.currentThread());
return true;
}
}
return false;
}
//公平锁实现方式
public boolean tryFairLock(int acquires) {
if (readCount.get() != 0){
return false;
}
if (writeCount.get() != 0){
//锁被占用(可能是自己)
if (Thread.currentThread() == reference.get()){
writeCount.set(writeCount.get()+acquires);//单线程 无需CAS
}
}else {
//为了实现公平锁,需要判断进来的线程是不是队列头部线程,不是则直接返回false(不让外来线程可乘之机)
if (queue != null){
if (queue.peek().thread == Thread.currentThread()
&& writeCount.compareAndSet(writeCount.get(),writeCount.get()+acquires)){
reference.set(Thread.currentThread());
return true;
}
}else{
if (writeCount.compareAndSet(writeCount.get(),writeCount.get()+acquires)){
reference.set(Thread.currentThread());
return true;
}
}
}
return false;
}
/**
* 尝试去解锁
* 解锁不用判断读锁占用情况:,修改count 和 reference
*/
@Override
public boolean tryUnlock(int releases){
if (reference.get() != Thread.currentThread()){
throw new IllegalMonitorStateException("未能获取到锁,无法释放锁");
}else {
//只有是拿到锁的线程才有解锁的资格,所以此处是单线程
int value = writeCount.get()- releases;
writeCount.set(value);
//当你lock多次,但是unlock一次,此时是不会释放锁,只是不阻塞罢了
if (value == 0){
reference.set(null);
return true;
}else {
return false;
}
}
}
}
public void lock(){
sync.lock();
}
public boolean tryLock(){
return sync.tryLock(1);
}
public void unlock(){
sync.unlock();
}
}
第三步:编写测试方法测试
public static void main(String[] args) {
MyReentrantLock lock = new MyReentrantLock();
new Thread(() -> {
lock.lock();
System.out.println("A开始");
LockSupport.parkNanos(1000000000*3L);
System.out.println("A结束");
lock.unlock();
}).start();
new Thread(() -> {
lock.lock();
System.out.println("B开始");
LockSupport.parkNanos(1000000000*3L);
System.out.println("B结束");
lock.unlock();
}).start();
new Thread(() -> {
lock.lock();
System.out.println("C开始");
LockSupport.parkNanos(1000000000*1L);
System.out.println("C结束");
lock.unlock();
}).start();
}
A开始
A结束
B开始
B结束
C开始
C结束
如果AQS中的变量不在同一个报下或者访问修饰符为private 导致你无法访问到MyAQS中的成员变量可使用反射拿到。比如:
MyAQS myAQS = new MyAQS();
Field queueField = myAQS.getClass().getDeclaredField("queue");
queueField.setAccessible(true);
LinkedBlockingQueue<MyAQS.WaitNode> queue = (LinkedBlockingQueue<MyAQS.WaitNode>) queueField.get(myAQS);
AQS实现读写锁
在文章开头已经实现了读写锁,这里我们再利用抽象出来的AQS再来实现一次,看能否达到一样的效果。
第一步:编写测试方法测试
/**
* @author heian
* @create 2020-03-07-4:18 下午
* @description
*/
public class MyReadWriteLock {
private boolean isFair;
private Sync sync;
public MyReadWriteLock(boolean isFair) {
this.isFair = isFair;
this.sync = new Sync();
}
public MyReadWriteLock() {
this.sync = new Sync();
}
//采取源码方式 内部类继承
class Sync extends MyAQS{
@Override
public boolean tryLock(int acquires){
return isFair == true ? tryFairLock(acquires):tryNonFairLock(acquires);
};
//非公平锁实现方式
public boolean tryNonFairLock(int acquires){
if (readCount.get() != 0){
return false;
}
if (writeCount.get() != 0){
//锁被占用(可能是自己)
if (Thread.currentThread() == reference.get()){
writeCount.set(writeCount.get()+acquires);//单线程 无需CAS
}
}else {
//非公平的实现
if (writeCount.compareAndSet(writeCount.get(),writeCount.get()+1)){
reference.set(Thread.currentThread());
return true;
}
}
return false;
}
//公平锁实现方式
public boolean tryFairLock(int acquires) {
if (readCount.get() != 0){
return false;
}
if (writeCount.get() != 0){
//锁被占用(可能是自己)
if (Thread.currentThread() == reference.get()){
writeCount.set(writeCount.get()+acquires);//单线程 无需CAS
}
}else {
//为了实现公平锁,需要判断进来的线程是不是队列头部线程,不是则直接返回false(不让外来线程可乘之机)
if (queue != null){
if (queue.peek().thread == Thread.currentThread()
&& writeCount.compareAndSet(writeCount.get(),writeCount.get()+acquires)){
reference.set(Thread.currentThread());
return true;
}
}else{
if (writeCount.compareAndSet(writeCount.get(),writeCount.get()+acquires)){
reference.set(Thread.currentThread());
return true;
}
}
}
return false;
}
/**
* 尝试去解锁
* 解锁不用判断读锁占用情况:,修改count 和 reference
*/
@Override
public boolean tryUnlock(int releases){
if (reference.get() != Thread.currentThread()){
throw new IllegalMonitorStateException("未能获取到锁,无法释放锁");
}else {
//只有是拿到锁的线程才有解锁的资格,所以此处是单线程
int value = writeCount.get()- releases;
writeCount.set(value);
//当你lock多次,但是unlock一次,此时是不会释放锁,只是不阻塞罢了
if (value == 0){
reference.set(null);
return true;
}else {
return false;
}
}
}
/**
* acquireShared()
* 尝试获取读锁
* 只修改count 因为reference是被多个线程引用
* 这里返回int 是为了模拟jdk源码
*/
@Override
public int tryLockShared(int acquires){
for (;;){
if (writeCount.get() != 0 ){//&& Thread.currentThread() != reference.get()
return -1;
}else {
int readNum = readCount.get();
//多个读线程 CAS操作改变,可能会失败所以需要自旋for
if (readCount.compareAndSet(readNum,readNum + acquires)){
return 1;
}
}
}
}
/**
* tryReleaseShared
* 尝试解开读锁,就直接修改readCount值即可
* 解锁不用判断写锁占用情况:读锁重入多次,解锁1次,则count就!=0,只有当读锁的count=0,才能表明读锁释放锁成功
*/
@Override
public boolean tryUnlockShared(int releases){
//这里无需去唤醒读锁,因为这里你解锁也只能唤醒一个,而是应该放在lockShared方法里,全部唤醒处在头部的读锁
for (;;){
int readNum = readCount.get();
int readNum2 = readNum - releases;
if (readCount.compareAndSet(readNum,readNum2)){
return readNum2 == 0;
}
}
}
}
public void lock(){
sync.lock();
}
public boolean unlock(){
return sync.unlock();
}
public void lockShared(){
sync.lockShared();
}
public boolean unlockShared() {
return sync.unlockShared();
}
}
第二步:编写测试方法测试
public static void main(String[] args) {
MyReadWriteLock lock = new MyReadWriteLock();
new Thread(() -> {
lock.lock();
System.out.println("写线程A开始");
LockSupport.parkNanos(1000000000*3L);
System.out.println("写线程A结束");
lock.unlock();
}).start();
new Thread(() -> {
lock.lock();
System.out.println("写线程B开始");
LockSupport.parkNanos(1000000000*3L);
System.out.println("写线程B结束");
lock.unlock();
}).start();
new Thread(() -> {
lock.lockShared();
System.out.println("读线程C开始");
LockSupport.parkNanos(1000000000*3L);
System.out.println("读线程C结束");
lock.unlockShared();
}).start();
new Thread(() -> {
lock.lockShared();
System.out.println("读线程D开始");
LockSupport.parkNanos(1000000000*3L);
System.out.println("读线程D结束");
lock.unlockShared();
}).start();
}
写线程A开始
写线程A结束
读线程C开始
读线程C结束
读锁唤醒下一个线程
写线程B开始
写线程B结束
读线程D开始
读线程D结束
因为线程进入队列的顺序不一致所以这是我当时运行的情况。OK今天就写到这里,下一篇写下基于JDK的AQS方式实现信号量Semaphone、计数器CountDownLatch、回旋栅栏CyclicBarrier。