带你走进 Java 成神之路(四十五)擅长的领域"/>
夜光带你走进 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或falsepublic boolean tryLock() {return sync.nonfairTryAcquire(1);}//这里,我们需要提一提 → 重载方法,在规定时间内获取锁,获取不到则返回falsepublic 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");//这里呢 我们将当前线程持有的锁+1setState(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);}//嗯唔//设置释放后的statesetState(c);return free;}
更多推荐
夜光带你走进 Java 成神之路(四十五)擅长的领域
发布评论