显式锁Lock与隐式锁synchronized比较:

  1. 可重入锁:都是可重入锁
  2. 可中断锁:隐式锁不可中断,而显示锁可以中断。
  3. 公平锁:隐式锁不是公平锁,显示锁在new一个对象的时候可以设置是否生成公平锁,如:new ReentrantLock(true);
  4. 读写锁:显式锁的读写锁,可以做到读读、读写不互斥,只有写写互斥,这样可以提升多线程时的资源使用率。

显式锁的优势:

显示锁的本质是通过双向链表实现的,显式锁是可以人为控制的,并且显式锁可以在读操作很多的情况下,通过使用读写锁来提高并发效率。

例如一个代码块被synchronized关键字修饰,如果有一个线程获取到了对应的锁,而释放锁的操作不是人为控制的,因此在代码执行完毕线程释放锁的时候,可能出现占有锁的线程正在等待IO或者其他原因(比如调用了sleep方法)被阻塞了, 此时就不能将锁释放,这样一次其他想要获取锁的线程只得一直等待获取锁。这样是非常浪费系统资源的。使用显式锁lock,可以通过调用该对象的unlock()方法来实现主动释放锁。 此外synchronized所有的操作 读读、读写、写写都是互斥的,有时候我们只希望写写互斥来提升系统的效率,显式锁ReentrantReadWriteLock就可以实现。

关于普通显式锁ReentrantLock的解析

ReentrantLock是读读、读写、写写都互斥的显式锁,这种锁在同一时间只能有一个线程占有锁资源。

ReentrantLock内部类Sync是显示锁实现同步控制的基础,Sync的基类是AbstractQueuedSynchronizer。在我的另一篇文章中有关于AbstractQueuedSynchronizer的详细解析(AbstractQueuedSynchronizer)。而Sync又是ReentrantLock实现公平锁和非公平锁的基础。

Sync源码解析

Sync使用AQS(AbstractQueuedSynchronizer类的简写)的状态值来表示持有锁的线程的数量。

abstract static class Sync extends AbstractQueuedSynchronizer {
        //公平和非公平方式获取锁都必须以这个方法为基础
        abstract void lock();
        
        //非公平方式获取锁的方法
        //从方法可以看出,非公平的方式获取锁的时候并不在乎线程请求锁的先后顺序,
        //只会判断线程请求锁时,锁是否被占有,锁没有被占该线程就可以马上成功拿到锁。
        //因此我们可以简单的理解非公平锁的获取完全靠线程的运气-。-。
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取当前AQS的状态,这里表示占有锁的线程数量
            int c = getState();
            if (c == 0) {
                //c等于0表示当前占有锁的线程数为0,这通过CAS方法设置state
                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;
            }
            //如果当前占有锁的线程数不为0,并且占有锁的线程不是当前线程,那么返回false
            return false;
        }
        //释放锁
        protected final boolean tryRelease(int releases) {
            //c表示当前线程释放锁后,占有锁的线程数量
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            //布尔类型的变量free用于占有锁的线程数是否为0
            boolean free = false;
            if (c == 0) {
                //如果为0则表示当前没有线程占有锁
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
        //判断当前线程是否是当前锁的独占线程
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
        //ConditionObject为AQS中的内部类,是实现条件队列(等待队列)的关键,我将在另一篇文章中做详细的解析
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        //获取当前锁的独占线程
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        //获取当前占有锁的线程数量
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
        //当前锁是否被占有
        final boolean isLocked() {
            return getState() != 0;
        }

        //重置锁
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

公平锁的实现源码

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        //公平的方法获取锁,对比下面的代码与nonfairTryAcquire非公平的方式获取锁的方法,可以发现
        //公平的方法获取锁,就是在获取锁时利用hasQueuedPredecessors判断当前线程之前有没有阻塞线程,没有的阻塞线程,当前线程才能获取锁
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
               /** hasQueuedPredecessors是AbstractQueuedSynchronizer中的方法,
                *  当前线程为等待锁的线程同步队列头节点或者线程同步队列为空,则返回false
                * 注意:我这里说等待锁的线程同步队列的头结点,而不是直接说AQS中的head节点
                *  通过AQS我们知道,获取锁的线程会构成一个节点链表形式的同步队列并且阻塞线程
                *  而在AQS中head节点代表整个链表的头结点,head节点为占有锁的线程构成的节点,            
                *  head节点之后的节点都是没有获取到锁的线程构成的阻塞节点,
                *  因此,等待锁的线程同步队列的头结点应该为head.next,即head节点的后继节点
                * 经过上面的分析,我们可以看出,hasQueuedPredecessors方法为false的两个条件:            
                *  1.线程同步队列为空,即head=tail,头节点和末节点相同.
                *  在AQS中enq方法源码可以看出head=tail时,同步队列为空,此时head与tail都为一个没有实际意义的节点
                *  2.当前线程为head.next节点中存放的线程
                */
                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;
        }
    }

 非公平锁的实现源码

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

可以看出非公平锁实际上就是在tryAcquire方法中调用了nonfairTryAcquire方法获取锁。 

关于ReentrantLock中lock方法和tryLock方法的不同

1.先看lock方法的源码

public void lock() {
        sync.lock();
    }

sync是ReentrantLock定义的内部类Sync类型的变量,经过追溯源码发现,不论是NonfairSync中的lock方法还是FairSync的lock方法,最终都是调用了父类AQS中的acquire方法(这里采用了模板方法模式,AQS为模板类),AQS中会将未获取到锁的线程包装成阻塞节点。因此,在普通锁ReetrantLock在通过lock方法尝试获取锁失败后,线程会进入同步队列并阻塞。

2.再看tryLock方法的源码

public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

在上面我们分析了ReentrantLock的内部类Sync,可以看出,nofairTryAcquire就是Sync自己内部定义的一个方法,并不是重写AQS的方法,因此nofairTryAcquire执行的结果只有true获取到锁或者false没有获取到锁两种结果,没有获取到锁的时候并不会阻塞线程而是直接返回false结果。

总结

显示锁中对于锁的控制主要还是在AbstractQueuedSynchronizer类中。经过上面的分析,公平锁与非公平锁简而言之,公平锁是所有线程都公平的获取锁的方式,线程拿到锁的顺序会根据其请求获取锁时的先后顺序,先请求能够优先占有锁。非公平锁有带有一定的随机性,如果当前线程在获取锁时,刚好遇见锁被另一线程释放,那么该线程就能获取到锁,并不会优先考虑正在等待锁的阻塞线程。