显示锁Lock与读写锁ReadWriteLock

我们先看顶层接口逻辑

           图1

       可以看到最顶层的接口分为Lock和ReadWriteLock,其中ReentrantLock实现了Lock接口,而ReentrantReadWriteLock实现的是ReadWriteLock接口,本文主要分析ReentrantLock。

       ReentrantLock和synchronized我们知道都有锁的功能,只不过ReentrantLock比synchronized更加灵活,可以实现尝试加锁,锁中断等功能。

一 初始化锁

@Service
public class TestLock {
    private static final Lock lock = new ReentrantLock();

    public void getLock(){
        System.out.println(Thread.currentThread().getName()+":获取锁");
        System.out.println("内存地址:"+VM.current().addressOf(lock));

        lock.lock();
        try{
            System.out.println("qwe");
            Thread.sleep(5000L);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getName()+":释放锁");
        }
    }
}

当代码运行到 new ReentrantLock()这行代码时虚拟机会将ReentrantLock对象实例化,我们看下实例化方法

                                 图2

 可以看到不加参数的实例化创建的是非公平锁,我们看非公平锁的实例化

 图3

我们再看 Sync继承自AbstractQueuedSynchronizer,AbstractQueuedSynchronizer里state字段会被赋初始值0,这个字段是加解锁的关键,先有个印象。

二 加锁

当代码运行到lock.lock()的时候我们跟进去

 

 

public final void acquire(int arg) {
    
    //尝试加锁
    if (!tryAcquire(arg) &&
       
       //如果加锁失败就放入队列
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
       
       //如果加锁失败并且已经放入队列就中断当前线程
       selfInterrupt();
}

 当第一个线程调用 lock.lock()的时候,尝试加锁肯定成功 acquire方法正常结束lock方法也正常结束,进入业务代码,此时第二个线程在第一个线程还没释放锁就进入到lock.lock()方法时,尝试加锁肯定失败就会addWaiter和acquireQueued 方法,我们看addWaiter和acquireQueued做了什么

 

    private Node addWaiter(Node mode) {
        
        //将当前线程封装成一个node节点
        Node node = new Node(Thread.currentThread(), mode);
        
        //取出aqs等待队列里的尾节点
        Node pred = tail;
        
        //如果aqs等待队列里的尾节点不为空,
        if (pred != null) {
    
            //则将当前线程node节点的头节点设置为aqs等待队列里的尾节点
            node.prev = pred;
            
            //采用cas模式将aqs等待队列里的尾节点设置为当前线程的node节点
            if (compareAndSetTail(pred, node)) {
                
                //将aqs等待队列里的尾节点的下一节点指向当前线程的node节点
                pred.next = node;
                return node;
            }
        }

        //如果aqs等待队列里的尾节点为空,将当前线程的node节点放入队列
        enq(node);
        return node;
    }

 节点封装完了就是放入aqs队列了,我们看acquireQueued方法

   final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {

                //取出当前节点的前置节点
                final Node p = node.predecessor();
                
                //如果前置节点==头节点就开始尝试加锁
                if (p == head && tryAcquire(arg)) {
                    
                    //如果加锁成功将当前节点设置为头节点
                    setHead(node);
                    
                    //将原来的头节点值为空帮助gc
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

三 解锁

    public final boolean release(int arg) {
        
        //尝试解锁
        if (tryRelease(arg)) {
            Node h = head;
            
           //如果头节点不为空,并且头节点的waitStatus不等于0,需要
           //将头节点的waitStatus 置为0,表示已解封该节点,等待下次获取锁
           if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

尝试解锁代码如下

      protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            
            //判断当前线程是否持有锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                //将当前线程改为不在持有锁
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

以上是个人对ReentrantLock的解读,希望有帮助

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值