美文网首页
ReentrantLock分析

ReentrantLock分析

作者: nzdxwl | 来源:发表于2019-11-23 11:32 被阅读0次

什么是ReentrantLock

ReentrantLock位于java.util.concurrent.locks包中,顾名思义,它是一个可重入锁的实现。
可重入锁:当一个线程调用某个对象的同步方法或同步代码块获得该对象的锁后,仍然可以继续调用该对象的其他同步方法或代码块而不用再次获取锁,这种锁就称为可重入锁。

synchronized关键字与锁对象的比较

使用synchronized标识的代码依靠一种简单的可重入锁来实现同步,java中每个对象都有一个内置锁,调用synchronized修饰的方法时,调用的线程会自动获得该对象的锁;当用于代码块时,则需要指定提供锁的对象。Java中的锁对象(Lock)工作原理与使用synchronized标识代码时使用的内置锁非常相似,一个锁对象的锁在同一时间仅有一个线程可以获得,不同的是锁对象可以使用在多种场景,通过代码来控制锁范围,而synchronized则通过虚拟机控制,锁对象还可通过关联的Condition对象支持唤醒/通知机制。

Synchronized code relies on a simple kind of reentrant lock.
Lock objects work very much like the implicit locks used by synchronized code. As with implicit locks, only one thread can own a Lock object at a time. Lock objects also support a wait/notify mechanism, through their associated Condition objects.

ReentrantLock实现的Lock接口


Lock接口只有上面6个方法,其中4个锁方法(直接获取锁、直接获取可中断锁,尝试获取锁,指定时长尝试获取锁),一个解锁方法,还有一个创建条件对象的方法。

ReentrantLock源码分析

ReentrantLock类内定义了两个类NonfairSync和FairSync,分别对应非公平锁和公平锁模式,我们对ReentrantLock的操作实际上是对这两个类中其中一个的操作,默认使用的是非公平锁模式,在创建ReentrantLock实例时调用带boolean参数构造器时传入true时则使用公平锁模式。

NonfairSync和FairSync继承抽象类Sync,Sync继承AbstractQueuedSynchronizer,AbstractQueuedSynchronizer继承AbstractOwnableSynchronizer。

以下是各个类的解析:

AbstractOwnableSynchronizer类

此类仅包含 transient Thread exclusiveOwnerThread变量的setter和getter方法,该变量用来保存当前取得锁的线程

public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {}
AbstractQueuedSynchronizer类

AbstractQueuedSynchronizer继承了上面的AbstractOwnableSynchronizer类,并定义了一个静态final类Node来作为队列节点,以及操作队列的相关方法;定义了public的内部类ConditionObject,它是Condition接口的一个实现,通过使用它来提供等待和唤醒功能

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {}
Condition接口
public interface Condition {
    void await() throws InterruptedException;  //等待直到被唤醒或者中断
    void awaitUninterruptibly();  //等待直到被唤醒
    boolean await(long time, TimeUnit unit) throws InterruptedException;  //等待直到被唤醒或中断,或者达到指定时长也会继续运行
    boolean await(long time, TimeUnit unit) throws InterruptedException; //功能与上个方法相同,但等待时间的设定较自由,可使用不同单位
    boolean awaitUntil(Date deadline) throws InterruptedException; //等待直到某个时间点
    void signal();  //唤醒在当前条件等待的一个线程
    void signalAll();  //唤醒在当前条件等待的所有线程
    
}
Sync抽象类

ReentrantLock类内的抽象类,继承了AbstractQueuedSynchronizer类,主要是实现了一个非公平尝试获取锁方法nonfairTryAcquire,并留下lock方法待实现。

abstract static class Sync extends AbstractQueuedSynchronizer{
    //...
    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;
        }
    //...
}
NonfairSync类

下面的state状态变量是AbstractQueuedSynchronizer类内的属性,方便说明这里放一起,非公平锁lock方法获取锁的做法就是先假定锁未被使用(状态是0),通过compareAndSetState方法如果状态是0就设置成1成功获得锁,获取失败会先采用非公平尝试nonfairTryAcquire去尝试,其实就是tryLock()操作,tryLock失败则进入队列等待;tryLock()方法实际执行的就是Sync类内的nonfairTryAcquire方法,获取锁会实时返回获取结果,不需要进入队列等待。

/**
     * The synchronization state.  
     */
    private volatile int state;

    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); //tryLock以及入队列判断
        }

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

FairSync类

公平锁模式与非公平锁模式的区别在于

  1. 获取锁的时候不会第一时间就尝试去设置锁状态
  2. 获取锁的时候,会先判断是否有其他线程排在前面位置要获取锁,如果没有才继续进行获取锁操作。
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        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;
        }
    }
其他
  • 如果对同一个锁获取多次,则每次获取状态加1,加锁与解锁需要匹配,即你获取一次就要有相应的解锁,如果没有可能会出现死循环或抛出异常等情况。
  • 无论ReentrantLock使用公平锁还是非公平锁模式,tryLock方法均使用非公平锁模式抢占锁。
唤醒和等待示例

以下示例通过使用ReentrantLock和Condition对象限定列表存储元素个数,当达到指定个数时不能继续存储,需要等待直到有元素被取出后才能继续操作。

class BoundedBuffer<T> {

    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();
    
    private int size = 5;
    public int getSize() {return size;}
    final List<T> items = new ArrayList<>(size);    
    
    public BoundedBuffer() { 
        
    }   
    
    public BoundedBuffer(int size) {
        this.size = size <= 0 ? this.size : size;
    }

    public void put(T x) throws InterruptedException {
        lock.lock();
        try {
            while (size == items.size())
                notFull.await();
            items.add(x);   
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (items.size() == 0)
                notEmpty.await();
            T x = items.get(0); //FIFO
            items.remove(0);
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

调用示例:

    public static void main(String[] args) throws InterruptedException {
        
        BoundedBuffer<Integer> bb = new BoundedBuffer<>(3);
        
        Runnable r1 = ()->{
            try {       
                //循环放入6个元素
                for(int i=1;i<7;i++) {
                    TimeUnit.SECONDS.sleep(3); //每隔3秒放入一个           
                    System.out.println("准备放入" + i + "....");                        
                    bb.put(new Integer(i));
                    System.out.println("成功放入元素:" + i + "-----");                        
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Runnable r2 = ()->{
            try {       
                //循环取出2个元素
                for(int i=0;i<2;i++) {                              
                    System.out.println("准备取出元素....");                       
                    Integer el = bb.take();
                    System.out.println("-------成功取出元素:" + el);
                    TimeUnit.SECONDS.sleep(8); //取出一个后休息8秒
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }               
        };
        
        //BoundedBuffer设置size=3 , 线程1放入6个元素,每3秒放一个,线程2取出2个元素,每取出一个休息8秒
        new Thread(r1).start();
        new Thread(r2).start();
        //由于size=3,要放入6个元素,但总共只取出2个元素,那么最后要放的元素放不进去,下面等待超过2次取出的时间后,再做一次取出动作
        TimeUnit.SECONDS.sleep(30);
        System.out.println("再次取出一个元素: " + bb.take());
        
    }

相关文章

网友评论

      本文标题:ReentrantLock分析

      本文链接:https://www.haomeiwen.com/subject/vlmbictx.html