Java公平锁与非公平锁的核心原理讲解
小威要向诸佬学习呀 人气:0Lock锁接口方法
前面了解到了synchronized锁,也知道了synchronized锁是一种JVM提供内置锁,但synchronized有一些缺点:比如不支持响应中断,不支持超时,不支持以非阻塞的方式获取锁等。而今天的主角Lock锁,需要我们手动获取锁和释放锁,里面有很多方式来获取锁,比如以阻塞方式获取锁,在指定时间内获取锁,非阻塞模式下抢占锁等,其方法源码如下(位于package java.util.concurrent.locks包下):
//Lock接口下的方法 public interface Lock { //阻塞式抢占锁,如果抢到锁,则向下执行程序;抢占失败线程阻塞,直到释放锁才会进行抢占锁 void lock(); //可中断模式抢占锁,线程调用此方法能够中断线程 void lockInterruptibly() throws InterruptedException; //非阻塞式尝试获取锁,调用此方法线程不会阻塞,抢到锁返回true,失败返回false boolean tryLock(); //在指定时间内尝试获取锁,在指定时间内抢到锁成功返回true,失败返回false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //释放锁,线程执行完程序后,调用此方法来释放锁资源 void unlock(); //条件队列 Condition newCondition(); }
公平锁简介
公平锁,顾名思义,所有线程获取锁都是公平的。在多线程并发情况下,线程争抢锁时,首先会检查等待队列中是否有其他线程在等待。如果等待队列为空,没有线程在等待,那么当前线程会拿到锁资源;如果等待队列中有其他线程在等待,那么当前线程会排到等待队列的尾部,好比我们排队买东西一样。
ReentranLock的公平锁
ReentranLock类实现了Lock接口,重写了里面的方法,对于ReentranLock类中的公平锁,我们可以看到如下源码:
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
在上述构造方法中,新建锁对象是否为公平锁,在于传入的参数是true还是false,在三目运算符中,如果传入参数为true,则会创建一个FairSync()对象赋值给sync,线程获取的锁是公平锁;如果为false则创建一个NonfairSync()对象,线程获取的锁是非公平锁。点入FairSync()方法,得到如下源码:
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } /*这里将acquire方法放于此 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); //拿到当前锁对象的状态 int c = getState(); //如果没有线程获取到锁 if (c == 0) { //首先会判断是否有前驱节点,如果没有就会调用CAS机制更新锁对象状态 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //设置当前线程拥有锁资源 setExclusiveOwnerThread(current); //如果获取锁资源成功则返回true return true; } } //或者如果拿到锁的就是当前线程 else if (current == getExclusiveOwnerThread()) { //将锁的状态+1,考虑到锁重入 int nextc = c + acquires; if (nextc < 0) //超过了最大锁的数量 throw new Error("Maximum lock count exceeded"); //如果锁数量没有溢出,设置当前锁状态 setState(nextc); //如果成功获取锁资源则返回true return true; } //如果前两条都不符合,则返回false return false; } }
上述代码涉及到了hasQueuedPredecessors()方法,返回true则代表有有前驱节点,点入查看得到以下源码:
public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; //h!=t,首节点不等于尾节点表示队列中有节点 return h != t && ( //s为头结点的下一个节点,返回false表示队列中还有第二个节点,或运算符后面表示,第二个线程不是当前线程 (s = h.next) == null || s.thread != Thread.currentThread() ); }
因此做出总结,使用ReentranLock的公平锁,当线程调用ReentranLock类中的lock()方法时,会首先调用FairSync类中的lock()方法;然后FairSync类中的lock()方法会调用AQS类(AbstractQueuedSynchronizer)中的acquire(1)方法获取资源,前面的文章里也提到过,acquire()方法会调用tryAcquire()方法尝试获取锁,tryAcquire()方法里面没有具体的实现,tryAcquire()方法具体逻辑是由其子类实现的,因此调用的还是FairSync类中的方法。在AQS中的acquire()方法中如果尝试获取资源失败,会调用addWaiter()方法将当前线程封装为Node节点放到等待队列的尾部,并且调用AQS中的acquireQueued方法使线程在等待队列中排队。
ReentranLock的非公平锁
非公平锁就是所有抢占锁的线程都是不公平的,在我们日常生活中就相当于是插队现象,不过也与插队稍微不同。在多线程并发时,每个线程在抢占锁的过程中,都会先尝试获取锁,如果获取成功,则直接指向具体的业务逻辑;如果获取锁失败,则会像公平锁一样在等待队列队尾等待。
在非公平锁模式下,由于刚来的线程可以在队首位置进行一次插队,所以当插队成功时,后面的线程可能会出现长时间等待,无法获取锁资源产生饥饿现象。但是非公平锁性能比公平锁性能更好。
对于ReentranLock类中的非公平锁,实现方式有两种,一种是默认的构造方式,另一种是和上面的一样,源码如下:
//第一种方式,默认实现 public ReentrantLock() { sync = new NonfairSync(); } //第二种方式,传入false来创建非公平锁对象 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
对于创建的NonfairSync()类对象,其源码如下:
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
在上述代码中,当线程获取锁时,并没有直接将当前线程放入等待队列中,而是先尝试获取锁资源,如果获取锁成功,设置state标志位1成功,则直接将当前线程拿到锁,执行线程业务;如果获取锁资源失败,则调用AQS中的acquire方法获取资源,而acquire()方法会回调上面NonfairSyn类中的tryAcquire()方法,然后又会回调Sync类中的nonfairTryAcquire()方法(NonfairSync类继承了Sync类) ,点击nonfairTryAcquire(acquires)查看源码:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
由上诉代码,没有将线程放入到等待队列中,只是对锁的状态进行了判断,若标识为0,代表没有线程拿到锁,当前线程会使用CAS机制改变锁状态,并调用setExclusiveOwnerThread(current)方法让当前线程拿到锁。
因此综上所述,在使用ReentranLock中的非公平锁时,首先会调用lock()方法,,而ReentranLock类中lock()方法会调用NonfairSync类中的lock()方法,接着NonfairSync类中的lock()方法会调用AQS中的acquire()方法来获取锁资源,AQS中的acquire()方法又会回调NonfairSync类中tryAcquire()方法尝试获取资源,NonfairSync类中tryAcquire()方法会调用Sync类中的nonfairTryAcquire方法尝试非公平锁获取资源;获取失败的话,AQS中的acquire()方法会调用addWaiter()方法将当前线程封装成Node节点放入到等待队列的队尾,而后AQS中的acquire()方法会调用AQS中的acquireQueued()方法让线程在等待队列中排队。这就是非公平锁的整个流程。
加载全部内容