夜光带你走进 Java 成神之路(四十五)擅长的领域

夜光序言:

 

深有体会~

人生之路是逼着走出来的,不逼自己一把,就永远不知道自己能做多大的事。

切断了退路,自然会想办法寻找出路;

掐断了幻想,才会埋头苦干。

逼着自己走出第一步,第二步、第三步就容易多了。

如果不逼自己,懒惰就会逐渐锈蚀自己的心,曾经的豪情万丈也会灰飞烟灭,生命的价值将会大打折扣。

 

正文:

                           以道御术 / 以术识道

ReentrantLock和synchronized

我们发现在实际开发中,看情况,并没有哪一种用的特别多

我更倾向于synchronized


并发编程不像ssm和ssh

还有什么实战,给你开发一个什么什么管理系统或者登陆

面试造火箭,实际工作拧螺丝

并发编程真正想搞明白

还是需要去看源码

ReentrantLock源码详细解读

AbstractQueuedSynchronizer(AQS源码解读)

大家可以有针对性的理解思考一下上面的问题

上面这个图很形象的介绍了各个关系

这里提一提:软件工程是一门科学,很多东西都需要我们程序员去实践,什么spring、springboot、springcloud,我们可以通过培训或者自学很容易入门

但是并发编程需要深入到里面下能有所体会,需要自己去实践

没有看源码,很多东西道听途说都是没用的~~~


Windows不开源

很多东西,我们都要学习linux

   

    首先呢:Sync:是提供AQS实现的工具,类似于适配器,提供了抽象的lock(),便于快速创建非公平锁。
    FairSync(公平锁):线程获取锁的顺序和调用lock()的顺序一样,FIFO。即先到先得


    NoFairSync(非公平锁):线程获取锁的顺序和调用lock()的顺序无关,抢到CPU的时间片即可调度。


这里,我们需要提一提:ReentrantLock中的重要方法

构造方法:无参构造方法,我们可以意识到 → 默认创建非公平锁;但是呢~~

有参构造方法,并且fair==true时,创建公平锁。
 


// 这里,我们可以看到
//维护了一个Sync,对于锁的操作
//我们发现,都交给sync来处理
    private final Sync sync;   
 
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

这里需要提一提:获取资源资源(锁)的方法

请求都是交给Sync来调度的。

    //首先呢
//    可以发现:请求锁资源,会阻塞且不处理中断请求,
    //这里,我们做一个假设,如果没有调用unLock(),则会一直被阻塞。
    public void lock() {
        sync.lock();
    }
//其次呢
//很重要的一个概念
    //有一种情况:线程在请求lock并被阻塞时
//这里,如果被interrupt,则此线程会被唤醒并被要求处理
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
//我们继续往下面看
//
    //尝试获取锁,默认获取的是非公平锁,失败后不会阻塞
    //直接返回true或false
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    //这里,我们需要提一提 → 重载方法,在规定时间内获取锁,获取不到则返回false
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

嗯唔~~      锁肯定需要释放,我们释放资源(锁)的方法:

不管是公平还是非公平锁,我们都会调用AQS.release(1)这个方法~~

给当前线程持有锁的数量-1。

//释放锁,给当前线程持有锁的数量-1  
  public void unlock() {
        sync.release(1);
    }

 

 

嗯唔~~

那个:主要讲非公平锁与公平锁获取资源的方法,因为释放资源的逻辑是一样的。

Sync来获取资源

我们需要知道:sync中定义了获取资源的总入口。但是呢,具体的调用还是看实现类是什么。

//调用的方法
abstract void lock();

我们看一看:非公平锁获取资源

嗯唔,看一看lock():获取锁时调用AQS的CAS方法,是阻塞的。

如果获取成功,则把当前线程设置为锁的持有者;如果获取失败,则通过AQS.acquire()获取锁。

我们程序员之前通过AQS源码详细解读,了解到acquire()中使用了模板模式,调用子类的tryAcquire()尝试获取锁

如果tryAcquire()返回false,则进入等待队列自旋获取,这之后呢,再判断前驱的waitStatus【这是一个很重要的概念】,判断是否需要被阻塞等。
 

tryAcquire():走的是Sync.nofairTryAcquire()。

    
//我们看一看这个代码嗯
//tryAcquire  布尔类型
    protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

我们再来看看下面这一段代码:

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
//这里,我们备注一个注释
            //state是0,锁处于空闲状态,使用CAS获取
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    //获取成功后,当前线程就是锁的持有者
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //这里呢,获取失败则先判断当前线程是否是锁的持有者
            //这里很重要,也是ReentrantLock实现可重入的原因
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                //可以看出源码设计者的厉害之处,考虑到了锁溢出的情况
                if (nextc < 0) 
                    throw new Error("Maximum lock count exceeded");
                //这里呢          我们将当前线程持有的锁+1
                setState(nextc);
                return true;
            }
            return false;
        }

nonfairTryAcquire(int acquires):这是什么意思呢,表示如果锁空闲,则用CAS修改state;

如果锁被占用,则判断占有者是不是自己,实现可重入。

那么~~          最终没有获取锁到就返回false。


下面,我们再看看:如何运用公平锁获取资源

lock():也是阻塞的。这里需要注意一下:

与非公平锁的区别是,我们呢~~

不能直接通过CAS修改state,而是直接走AQS.acquire()。

//方法差不多就是这样嗯
//参数为1     
   final void lock() {
            acquire(1);
        }

这里,我们看一看          →        tryAquire():与非公平锁类似,AQS.acquire()会调用这个钩子方法。

但是呢,我们发现多判断了hasQueuedPredecessors(),判断当前节点在等待队列中是否有前驱节点,如果有,则说明有线程比当前线程更早的请求资源,根据公平性

嗯唔~~         当前线程请求资源失败;如果当前节点没有前驱节点,才有做后面的逻辑判断的必要性。
 


        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //下面这段源代码,很多老师都讲过该如何理解
                //我们可以看一下
                //!hasQueuedPredecessors()判断等待队列中是否有前驱节点,没有则尝试获取锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
//下面该如何理解呢,我们看一下
            //实现可重入
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

 

下面,我们看一下:

ReentrantLock有三种获取锁的方法,lock(),tryLock(),lockInterruptibly()。

这里我们提一提获取资源   →  tryLock():走的还是sync的方法,主要是在指定时间内获取锁,直接返回结果。


    
// tryLock():走的还是sync的方法,主要是在指定时间内获取锁,直接返回结果。
public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    
//我们看看下面这一段代码
//看看如何理解
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
//tryAcquireNanos():如果调用tryLock的规定时间内尝试方法,就会调用该方法
//挺有意思的
//这里,我们先判断是否中断,然后尝试获取资源,否则进入AQS.doAcquireNanos()
//在规定时间内自旋拿资源,[这里注意一下:自旋的次数是由计算机学者决定的]
//拿不到则挂起再判断是否被中断。
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

嗯唔~~         这里我们看一下:lockInterruptibly()--获取锁时响应中断

lockInterruptibly():交给了调度者sync执行。

    //我们看一下这个方法

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

我们看一下:acquireInterruptibly()    →    嗯唔~~这里当尝试获取锁失败后,之后,我们就进行阻塞可中断的获取锁的过程。

这里,我们调用AQS.doAcquireInterruptibly()这个方法

很厉害嗯~~


  //我们看下下面这个方法
// Thread.interrupted() 阻塞

   public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

我们可以知道:公平锁与非公平锁的释放都是一样的。

我们可以知道:ReentrantLock.release()调用的是sync.release(1)。

所以呢:本质还是进入AQS.release(1),下面看看其中的tryRelease()这个钩子方法如何实现。


嗯唔~~:Sync释放资源

tryRelease():尝试释放锁,彻底释放后返回true。
 

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //需要注意一下:释放锁必须保证当前线程是锁的持有者
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //注意看下状态值
            //如果释放后状态值为0,则彻底释放,持有者被设置为空
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //嗯唔
            //设置释放后的state
            setState(c);
            return free;
        }
发布了1477 篇原创文章 · 获赞 281 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/weixin_41987706/article/details/103850203