ReadWriteLock读写锁加锁过程

读写锁案例 + 小小总结

//读这篇文章的时候,建议先看一下,我并发专题中的 Lock.lock() 加锁的过程的文章
//我在写读写锁的时候,好多东西,好多理念都在lock()中体现
//	读读串行
//	读写串行
//	写写串行

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {

    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Integer data = 0;

    public void get(){

        readWriteLock.readLock().lock();
        System.out.println(Thread.currentThread().getName() + "ready read");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "数据 = " + data);
        readWriteLock.readLock().unlock();
    }

    public void put(Integer data){

        readWriteLock.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + "ready write");
        try {
            Thread.sleep(1000);
            this.data = data;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "写入的数据 = " + data);
        readWriteLock.writeLock().unlock();
    }

    public static void main(String[] args) {

        final ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(()->readWriteLockDemo.get()).start();
            new Thread(()->readWriteLockDemo.put(new Random().nextInt(10))).start();
        }
    }
}

/**
	put这个操作 加了写锁,写锁是排他锁,加上写锁后,不能在加其它锁,加上写锁后,读锁加上后不能加其它任何锁
	
	get 读锁 可以允许很多读写进来的
	
	
	Thread-11ready write
	Thread-11写入的数据 = 8   这两个输出是不能分开的,write锁是排他锁
	
	Thread-2ready read
	Thread-6ready read
	Thread-4ready read
	
	读锁,这个是能分开的
	
	为什么会这样呢? 下面源码解释一下
		
*/

其实,不管是lock也好,读写锁也罢,都是有一个状态state

这个state在lock中 0 初始状态 1、加锁状态 N(N > 1)重入锁

在读写锁中,也是有state这个状态的,但是,这个state是由32个bit位组成的,

0000 0000 0000 0000 0000 0000 0000 0000

前16个bit 读锁

后16个bit 写锁

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.readLock().lock();
System.out.println("rrrrrrrrrrrrrr");
readWriteLock.writeLock().lock();
System.out.println("wwwwwwwwwwwwww");

//看到这个代码的时候,我有一个想法?
//首先,我是读锁,输出了 rrrrrrrrrrrrrr
//readWriteLock.writeLock().lock();  我能否升级一下呢?  把读锁升级为写锁  当然是 不能
//为什么锁不能升级?
//因为,读锁可以并发,如果,现在有两个线程,t1 t2两个线程现在都是读锁,要升级写锁
//t1在等待t2释放锁,t2在等t1释放锁,死锁了

//读锁是共享锁,比如有100个线程同时占有这个锁,都在等待对方释放锁,无法保证100%释放锁。

//一旦写锁成功上锁, 写锁是排它锁,state从0变成1,只有一个写线程



ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.writeLock().lock();
System.out.println("wwwwwwwwwwwww");
readWriteLock.readLock().lock();
System.out.println("rrrrrrrrrrrrr");

//那,既然锁升级不能行,那锁降级呢?  其实是可以的
//锁降级就可以了,因为写锁是排他锁,可以保证让读100%占用到锁。
//锁降级,既然写锁是排它锁,为什么这个读锁不能等写锁释放再拿?
//因为,它一旦释放了锁,瞬间就会有很多线程来抢占这个state,你能否抢到就不一定了

写锁加锁过程

//这边和Lock.lock() 是一模一样的
public final void acquire(int arg) {
    
    //tryAcquire(arg) 尝试加锁失败,和Lock.lock()一模一样,初始化同步队列
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

static final int SHARED_SHIFT   = 16;
//1 << SHARED_SHIFT			1 0000 0000 0000 0000
//EXCLUSIVE_MASK			0 1111 1111 1111 1111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;

static int exclusiveCount(int c) { 
    
    return c & EXCLUSIVE_MASK; 
}

protected final boolean tryAcquire(int acquires) {
    
    Thread current = Thread.currentThread();	//拿到当前线程
    //第一个线程进来,c肯定为0
    int c = getState();
    //把写的状态拿出来
    int w = exclusiveCount(c);
    //c != 0 证明已经上锁成功了  可能是写锁 也可能是读锁
    if(c != 0){
        //w == 0 证明加的不是写锁
        //current != getExclusiveOwnerThread()  不是重入锁
        if (w == 0 || current != getExclusiveOwnerThread()){
        	return false;
        }
        //这把锁能重入的最大次数
        if (w + exclusiveCount(acquires) > MAX_COUNT){
            throw new Error("Maximum lock count exceeded");
        }
        //这个是锁重入
        setState(c + acquires);
        return true;
    }
    //writerShouldBlock()  这个判断是  是否需要排队
    //如果,不需要排队,看看能否加锁成功
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires)){
        return false;
    }
    //这边肯定是已经加锁成功了,设置当前线程
    setExclusiveOwnerThread(current);
    return true;
}

//这个和lock() 里面的那个东西是一模一样的
//就是看看 时候需要等待
public final boolean hasQueuedPredecessors() {
    
    Node t = tail;
    Node h = head;
    Node s;
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}

读锁加锁过程

private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
private transient HoldCounter cachedHoldCounter;
private transient ThreadLocalHoldCounter readHolds;
static final int SHARED_SHIFT   = 16;

static int sharedCount(int c) { 
    return c >>> SHARED_SHIFT;
}

static final class HoldCounter {
    
    int count = 0;
    final long tid = getThreadId(Thread.currentThread());
}

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0){
        doAcquireShared(arg);
    }
}

protected final int tryAcquireShared(int unused) {
    
    Thread current = Thread.currentThread();
    int c = getState();
    //exclusiveCount(c) != 0  这个是用来判断是否有写锁的 != 0 证明是有写锁的
    //getExclusiveOwnerThread() != current 但是加锁线程不是当前线程
    //因此,有写锁了,但是进来的又不是那个线程(不是重入锁),加锁失败,返回 -1
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current){
        return -1;
    }
    //如果还没有加锁,c为0 右移16位,r为0
    //如果已经加锁了,此时肯定是读锁,c不为0,右移16位,r不为0
    //readerShouldBlock() 读锁是共享锁,为什么还要去判断有没有等待队列?
    	//就是为了防止,此时,有写锁在排队
    //r < MAX_COUNT  不能达到最大值
    //compareAndSetState(c, c + SHARED_UNIT) 
    // static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    int r = sharedCount(c);
    if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
        
        //r == 0 证明还没有加锁过,不管是读锁 还是 写锁  都没有
        //firstReader == current 这个严格意义上来说不是重入锁,只算是firstReader的次数(下面图片解释)
        //这边的步骤,建议你配置着下面的图,进行逻辑分析一下。
        //第一个线程(读锁)就是上面的那个
        //那其他的呢?HoldCounter rh = cachedHoldCounter;  这个HoldCounter里面维护了一个count
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        }else {
            //其他线程,整了一个缓存数据结构  cachedHoldCounter
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current)){
                //为当前线程初始化一个rh
                //有个疑问?那么多线程,怎么区分呢? ThreadLocal
                cachedHoldCounter = rh = readHolds.get();
            //当前线程已经被初始化对象了
            //count + 1 设置 rh对象
            }else if (rh.count == 0){
                readHolds.set(rh);
            }
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

在这里插入图片描述

​ 上面的图解释了,为什么上面说的 那个 严格意义上来说 不是重入锁?

​ 在Lock.lock() 源码中,如果c为 > 1 就是锁的重入,这个读写锁就不一定是了。

​ 读锁是共享锁,每次来一个读锁,都会把 r + 1 的 ,因此,这个r就是个总和。

​ 如果说,想看看哪个线程是重入的,就是自己线程维护的 ReaderHoldCount 这个值就行了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值