java并发包之ReentrantLock

2017-12-01  本文已影响0人  zoengyunhing

一、前言

在上文中谈到了AQS是Lock实现的前提,而本文说到的ReetrantLock就是在此基础上处理的。而本文中会对ReetrantLock公平性,可重入性等进行介绍。

二、特性

2.1 可重入

可重入性也就是能够让线程多次进行锁的获取操作,首先举个例子:

public class ReentranDemo {

    private ReentrantLock lock = new ReentrantLock();

    public void putDemo(){
        try {
            lock.lock();
            System.out.println("putDemo"+Thread.currentThread().getName());
            reInDemo();
        }finally {
            lock.unlock();
        }
    }

    public void reInDemo(){
        lock.lock();
        try {
            System.out.println("reInDemo"+Thread.currentThread().getName());
        }finally {
            lock.unlock();
        }
    }
     public static void main(String[] args) {
            new Thread(){
                public void run(){
                    new ReentranDemo().putDemo();
                }
            }.start();
    }
}

运行结果

putDemoThread-0
reInDemoThread-0

如果ReentrantLock不是可重入锁的话,那么该线程会进入死锁。

2.2 公平性

公平锁:线程获取锁资源的顺序为先后调用lock方法的顺序依次获取锁
非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

看个例子:

public class FairLockDemo {

    private static Lock fairLock = new ReentrantLock(true);
    public void fair() {
        for (int i = 0; i < 5; i++) {
            new Thread(){
                public void run(){
                    for (int i = 0; i < 5; i++) {
                        fairLock.lock();
                        try {
                            System.out.println(Thread.currentThread().getName() + "is locking " );
                        } finally {
                            fairLock.unlock();
                        }
                    }
                }
            }.start();

        }

    }
    public static void main(String[] args) {
        FairLockDemo fairLockDemo = new FairLockDemo();
        fairLockDemo.fair();
    }  
}

返回结果如下:

Thread-0is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-0is locking 
Thread-4is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-0is locking 
Thread-4is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-0is locking 
Thread-4is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-0is locking 
Thread-4is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-4is locking 

如例子所示,连续获取的情况基本没有

2.3 非公平锁

加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

看个例子:

public class UnfairLockDemo {

    private static Lock unfairLock = new ReentrantLock();

    public void unfair() {
        for (int i = 0; i < 5; i++) {
            new Thread(){
                public void run(){
                    for (int i = 0; i < 5; i++) {
                        unfairLock.lock();
                        try {
                            System.out.println(Thread.currentThread().getName() + "is locking " );
                        } finally {
                            unfairLock.unlock();
                        }
                    }
                }
            }.start();

        }

    }

    public static void main(String[] args) {
        UnfairLockDemo unfairLockDemo = new UnfairLockDemo();
        unfairLockDemo.unfair();
    }

}

返回结果如下:

Thread-0is locking 
Thread-3is locking 
Thread-3is locking 
Thread-3is locking 
Thread-3is locking 
Thread-4is locking 
Thread-4is locking 
Thread-4is locking 
Thread-4is locking 
Thread-4is locking 
Thread-1is locking 
Thread-1is locking 
Thread-1is locking 
Thread-1is locking 
Thread-1is locking 
Thread-2is locking 
Thread-2is locking 
Thread-2is locking 
Thread-2is locking 
Thread-2is locking 
Thread-0is locking 
Thread-0is locking 
Thread-0is locking 
Thread-0is locking 
Thread-3is locking 

而我们的非公平锁出现连续获取锁的情况却非常多。而出现这种情况的原因会在后文源码分析中介绍到。

三、主要类与方法分析

3.1 Sync

在看源码之前我们先略微介绍下该类,这个类是继承于AQS,而Sync还有两个子类,分别是公平锁和非公平锁的两种不同的实现。

 abstract static class Sync extends AbstractQueuedSynchronizer {

        //具体实现由非公平和公平进行实现
        abstract void lock();
        //非公平尝试获取
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取AQS锁资源的数量
            int c = getState();
            //如果为0表示没有线程获取资源
            if (c == 0) {
                //利用CAS将state为0修改为当前资源数,并将当前的线程记录下来
                if (compareAndSetState(0, acquires)) {       
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果state>0且当前线程和记录的线程是同一个,则重入处理(这里就是重入锁的相应实现)
            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;
        }
        //尝试释放锁资源
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
        //是否独占
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
        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); 
        }
    }

nonfairTryAcquire在后文会看到非公平锁对其的调用,而内部实现的处理正是我们可重入锁的具体处理方案。

3.2 FairSync

接下来是我们的公平锁的类,他实现了其父类Sync的lock方法。也同时对上一篇文章AQS的tryAcquire进行了重写

static final class FairSync extends Sync {
        //获取锁,内部主要的实现由AQS的acquire实现
        final void lock() {
            //表示会获取一个锁资源,其中一部门就是由FairSync类下的tryAcquire实现具体实现可以看下上一篇文章AQS的acquire方法
            acquire(1);
        }
        //对基类AQS的重写,表示获取资源
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //状态为0表示没有线程获取锁
            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;
        }
    }

    public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

由于lock的主要逻辑在AQS的acquire中,而tryAcquire方法的重写,展示了Reetrantlock公平锁在获取资源与其它同步锁不同的特性。通过调用的acquire表示其是一个独占锁,而通过判断当前线程与记录线程的比较实现的是Reetrantlock可重入性。而最后判断当前节点是否有前置节点来实现了我们的公平性。

3.3 NonfairSync

static final class NonfairSync extends Sync {
       
        final void lock() {
            //如果能获取锁直接获取。
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //获取不了通过nonfairTryAcquire进行锁资源获取
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            //sync类中有实现
            return nonfairTryAcquire(acquires);
        }
    }

公平锁和非公平锁的区别:
首先公平锁获取直接调用acquire方法,而该方法不是直接获取锁,而是调用的tryAcquire方法,而公平锁的tryAcquire首先获取state状态吐过没有线程占用(state=0),会判断在AQS的等待队列中的该节点的前置节点是否为其他同样等待的节点,如果有就说明在公平性上其他节点已经排好队了,那么会让出获取资源的权利。
而非公平锁,一上来就直接判断state状态,完全是一种抢占的机制。如果抢占不了在调用acquire方法,而非公平的acquire中的tryAcquire同样不会像公平锁的tryAcquire方法判断节点的前置节点。

3.4构造函数

    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

可以指定ReentrantLock的公平性

3.5 lock方法

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

直接调用公平锁或者非公平锁的lock

3.6 tryLock

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

尝试获取锁,不会发生阻塞,成功或失败直接返回。

3.7 unlock

public void unlock() {
    sync.release(1);
}

释放锁直接调用AQS的release释放。

上一篇下一篇

猜你喜欢

热点阅读