首页 > 编程知识 正文

互斥锁和悲观锁区别,互斥锁用于线程的互斥

时间:2023-05-03 05:23:00 阅读:148890 作者:3884

*首先,互斥锁实现什么功能? ***

一个独占锁需要阻塞和唤醒功能,实现阻塞和唤醒功能需要什么因素?

需要标记锁定状态的state变量。

需要记录哪些线程有锁。

需要维护所有线程的队列。

另外,为了实现线程安全,在state和队列中使用了CAS。

如果具备以上3个条件,就可以实现线程的阻塞和唤醒。

*那么,Java是如何实现互斥的? ***

首先,Java独占锁由ReentrantLock类定义,因为所有锁基本上都实现了重新读取功能。 这意味着可以重新读取。 这意味着ReentrantLock类的锁定是独占锁定。 ReentrantLock很重要,但ReentrantLock没有代码逻辑。 ReentrantLock的具体实现是NonfairSync和FairSync,ReentrantLock在构建方法中也是new的NonfairSync或FairSync,另外,lock和unlock是被调用的sync.lock sync的lock是抽象方法,分别由NonfairSync和FairSync实现,sync.release是由sync继承的abstractqueuedsync

首先,说明ReentrantLock中的继承结构。 ReentrantLock实现了Lock接口,将Lock接口中的Lock (,lockInterruptibly )、unlock (unlock )、tryLock (trylock ) )、trylock ) 然后,ReentrantLock引用Sync抽象内部类,Sync分别由NonfairSync和FairSync实现,Sync继承AbstractQueuedSynchroinzer队列同步器,abstranstraction

在讨论了ReentrantLock之后,ReentrantLock如何实现互斥?

1.State,state在AbstractQueuedSynchronized中定义,用于记录线程进入此锁的次数。

2 .记录哪些线程具有锁,并为AbstractOwnerSynchronized定义Thread类型的exclusiveOwnerThread (独占所有者线程)以记录锁的所有线程。

3 .队列在AbstractQueuedSynchronized中主要用于存储线程,定义为实现线程管理的双向链表中实现的队列。

如何在这些条件下实现阻滞和清醒? (涉及公平和不公平) )。

阻塞是在什么情况下发生的? 线程夺取锁,成功访问资源,夺回锁失败而阻塞。 唤醒? 从块队列中醒来后,尝试解锁。 如果不访问资源并解除锁定,则会再次被阻止。 (两种醒来方式unpark(threadt )、谦虚的乐曲) (两种方式都需要在不同的线程上运行)。

在什么情况下线程会夺取锁? 多个线程试图调用lock方法来给自己上锁。 如果试过之后没有抢劫会怎么样? 只能屏蔽了。 让我们看看NonfairSync和FairSync最终如何实现lock方法。

staticfinalclassnonfairsyncextendssync { privatestaticfinallongserialversionuid=7316153563782823691 l;/* * * performs lock.tryimmediatebarge,backinguptonormal * acquireonfailure.*/finalvoidlock () if ) compareandsetstststststtttore } protectedfinalbooleantryacquire (int acquires ) returnnonfairtryacquire ) acquires; } staticfinalclassfairsyncextendssync { privatestaticfinallongserialversionuid=-300089789709046540

L; final void lock() { acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { 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; } }

我们可以看到,不管是NonfairSync或者FairSync中的lock方法最后都要执行acquire(int arg)方法,acquire翻译过来就是获取的意思,就是获取锁,acquire是继承自AbstractQueuedSynchronized的方法,代码如下:

public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}

我们其实可以从方法名中看出acquire方法内部是获取锁的逻辑,但是非公平锁很不讲道理的在还没有进入acquire方法正式抢锁之前就已经尝试抢过一次锁了。进入acquire方法中,首先进入一个tryAcquire()尝试抢锁方法,这个方法在AbstractQueuedSynchronized中有定义,但是被NonfairSync和FairSync分别重写了,其中NonfairSync的tryAcquire方法中又返回了Sync中的nonfairTryAcquire方法,表明,NonfairSync类中的tryAcquire方法的主体逻辑写在了Sync类中,FairSync类中的tryAcquire方法中的逻辑还是正常的在自己的类中。

回来,我们继续说tryAcquire()方法有一个返回值,就是获取到锁返回true,没有获取到锁返回false,后面的acquireQueued方法是一个阻塞逻辑。if里面的逻辑就是,如果抢锁失败,返回false,执行acquireQueued()方法阻塞当前线程,如果抢锁成功就已经完成了目标不执行后面的阻塞逻辑。那么,tryAcquire(int arg)方法中arg总是1,意味着每抢锁成功一次加一,tryAcquire中的总体逻辑是,如果state值为0,那么在非公平锁中直接尝试设置state为1,设置成功就将标记独占者线程的Thread类型属性改为当前线程,完成抢锁,成功获得标记,没有被阻塞。如果state值不为0,那么就使state加1并且更新state的值,更新state值之前如果发现加一之后state小于0,则抛出锁的最大数量已经被超出了错误,一般不会发生,因为上限有20多亿(超过上限加一,变为负数,计算机底层二进制计算原理,应该是首位符号位变了);而在公平锁中,在state===0 时需要判断当前线程是否在阻塞队列头,如果当前线程在队列头,或者队列为空,则该线程可以抢锁。state不为零时和NonfairSync一样判断一下当前线程是被标记的独占者线程后,如果是就可以直接操作state,重入时不需要判断阻塞队列的情况。

获取锁失败之后,线程进入acquireQueued方法的逻辑中,acquireQueued方法中有一个addWaiter方法先将线程加入阻塞队列的最后一个节点,加入阻塞队列后并不会发生阻塞,只是记录了将线程记录到队列里面,等到线程进入acquireQueued方法才会阻塞。acquireQueued方法的内部的主体逻辑就是一个for死循环的内容,循环中首先判断线程所在节点是不是队列中的第一个节点,方法里用的是判断当前节点的前驱节点是不是head节点,如果是,则表明该节点位于队列第一个,则尝试获得锁,如果获得锁成功,就将该节点设置为头结点(也就是出队列,使head指针右移,并且是节点的Thread属性设置为null,也就是head还是指向了一个空节点),然后把p.next设置为null便于GC,最后返回一个布尔值判断该线程是否被中断过。如果线程没有获取到锁,或者根本不在队列第一位,那么直接跳到一个if()逻辑里,里面有两个方法,shouldParkAfterFailedAcquire(...)判断获取锁失败后应不应该阻塞,如果应该阻塞,则执行parkAndCheckInterrupt()方法,parkAndCheckInterrupt()方法里的逻辑也很简单,直接调用LockSupport.park(this)阻塞该线程,然后返回一个Thread.interrupted(),判断线程是否被中断过,如果被中断过,返回true,进入if下面的逻辑,设置中断标志值为true,表示该线程被中断过,acquireQueued方法就会返回true,就会执行selfInterrupt方法中断自己,自己的状态就会编程已被中断过状态;如果该线程没有被中断过,则Thread.interrupted()返回false,不执行if下面的逻辑中断标志值就为false,那么acquireQueued方法就会返回false,不会使用selfInterrupt()方法,也就不会把自己设置为已被中断过状态。之所以要自己中断自己是因为,线程被阻塞期间即使被中断了,也没有完成把自己的状态设置为已被中断过的状态。

以上是对lock()方法的介绍,下面我们来看看unlock() 方法的实现,lock()方法是获取锁,谁获取到锁谁访问资源,unlock()方法是释放锁,只有拥有锁的线程才能释放锁,否则会产生IllegalMonitorStateException;unlock()方法中返回了sync.release(1),而sync.release(1)是从AbstractQueuedSynchronized类中继承的方法。release(int arg)方法主要逻辑由两个方法执行,一个是tryRelease(int arg)方法和unparkSuccessor(Node node)方法,tryRelease(int arg)尝试释放锁,每调用一次state减一,如果state==0,那么使用set ExclusiveOwnerThread(Thread thread)方法,将exclusiveOwnerThread设置为null,表示没有线程持有该锁。释放锁成功则返回true,就绪执行if中的逻辑,里面逻辑大致为如果队列中有节点的话,就调用unparkSuccessor(Node node)唤醒该方法。也就是唤醒后继节点,使其尝试获取锁。

再来看一个方法lockInterruptibly()实现分析,其实和lock的几乎一样,唯一的区别就是可被interrupt()方法唤醒,怎么实现的呢?就是在parkAndCheckInterrupt()方法那里当Thread.interrupted()返回为true进入if下的逻辑时,把lock时设置的中断标志变量,改为抛出一个异常,那么就实现了可被中断的作用。

最后是一个tryLock方法,ReentrantLock中的tryLock方法返回一个sync.nonfairTryAcquire(1)方法,sync.nonfairTryAcquire(int arg)会尝试获取锁,如果没有获取锁成功,会返回一个false,获取成功就会返回true,sync.nonfairTryAcquire(int arg)中只尝试获取锁一次,并不循环,然会返回true或false,再由tryLock方法返回这个值。需要注意的是非公平锁的lock其实也用了这个方法获取锁,但是如果返回false,lock中的代码会使的线程继续进入acquireQueued方法阻塞。

 

 

 

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。