前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java的重入锁ReentrantLock

Java的重入锁ReentrantLock

作者头像
用户3467126
发布2019-07-19 11:44:00
5470
发布2019-07-19 11:44:00
举报
文章被收录于专栏:爱编码

简介

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。

在java关键字synchronized隐式支持重入性。synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。

核心概念

1. 重入性

重入性关键点在于以下两个方面:

1、在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功; 2、由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。

2. 公平锁和非公平锁

ReentrantLock支持两种锁:公平锁和非公平锁。何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO。ReentrantLock的构造方法无参时是构造非公平锁。

这些特性是如何做到的,那就看下面的源码分析,以下部分建议先看上一篇文章再看理解起来或许简单点

源码分析

1 类的继承关系 

代码语言:javascript
复制
public class ReentrantLock implements Lock, java.io.Serializable

说明:ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。

2 类的内部类

 ReentrantLock总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。

  说明:ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。下面逐个进行分析。

2.1 Sync类
代码语言:javascript
复制
abstract static class Sync extends AbstractQueuedSynchronizer {
        // 序列号
        private static final long serialVersionUID = -5179523762034025860L;

        // 获取锁
        abstract void lock();

        // 非公平方式获取
        final boolean nonfairTryAcquire(int acquires) {
            // 当前线程
            final Thread current = Thread.currentThread();
            // 获取状态
            int c = getState();
            if (c == 0) { // 表示没有线程正在竞争该锁
                if (compareAndSetState(0, acquires)) { // 比较并设置状态成功,状态0表示锁没有被占用
                    // 设置当前线程独占
                    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;
        }

        // 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
        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() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        // 新生一个条件
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class
        // 返回资源的占用线程
        final Thread getOwner() {        
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        // 返回状态
        final int getHoldCount() {            
            return isHeldExclusively() ? getState() : 0;
        }

        // 资源是否被占用
        final boolean isLocked() {        
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        // 自定义反序列化逻辑
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

说明:Sync类存在如下方法和作用如下。

2.2. NonfairSync类

NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法,源码如下。

代码语言:javascript
复制
// 非公平锁
    static final class NonfairSync extends Sync {
        // 版本号
        private static final long serialVersionUID = 7316153563782823691L;

        // 获得锁
        final void lock() {
            if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用
                // 把当前线程设置独占了锁
                setExclusiveOwnerThread(Thread.currentThread());
            else // 锁已经被占用,或者set失败
                // 以独占模式获取对象,忽略中断
                acquire(1); 
        }

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

 说明:从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。

2.3. FairSyn类

FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法,源码如下

代码语言:javascript
复制
// 公平锁
    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) { // 状态为0
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
                    // 设置当前线程独占
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据
                // 下一个状态
                int nextc = c + acquires;
                if (nextc < 0) // 超过了int的表示范围
                    throw new Error("Maximum lock count exceeded");
                // 设置状态
                setState(nextc);
                return true;
            }
            return false;
        }
    }

说明:跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。

  说明:可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。这也是和Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。

案例

代码语言:javascript
复制
public class ReentrantLockTest {

    private static Lock lock = new ReentrantLockMine(false);    //非公平锁
//    private static Lock lock = new ReentrantLockMine(true);   //公平锁

    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        String lockType = "非公平锁";
//        String lockType = "公平锁";
        long start = System.currentTimeMillis();
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Time(lockType, start));     //10个线程执行完毕时,执行Time线程统计执行时间

        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Job(lock, cyclicBarrier)){
                public String toString() {
                    return getName();
                }
            };
            thread.setName("" + i);
            thread.start();
        }


    }

    private static class Job implements Runnable{
        private Lock lock;
        private CyclicBarrier cyclicBarrier;
        public Job(Lock lock, CyclicBarrier cyclicBarrier) {
            this.lock = lock;
            this.cyclicBarrier = cyclicBarrier;
        }

        public void run() {
            for (int i = 0; i < 10; i++) {
                lock.lock();
                try {
                    System.out.println(i+"获取锁的当前线程[" + Thread.currentThread().getName() + "], 同步队列中的线程" + ((ReentrantLockMine)lock).getQueuedThreads() + "");
                } finally {
                    lock.unlock();
                }
            }
            try {
                cyclicBarrier.await();  //计数器+1,直到10个线程都到达
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    private static class ReentrantLockMine extends ReentrantLock {  //重新实现ReentrantLock类是为了重写getQueuedThreads方法,便于我们试验的观察
        public ReentrantLockMine(boolean fair) {
            super(fair);
        }

        @Override
        protected Collection<Thread> getQueuedThreads() {   //获取同步队列中的线程
            List<Thread> arrayList = new ArrayList<Thread>(super.getQueuedThreads());
            Collections.reverse(arrayList);
            return arrayList;
        }
    }


    private static class Time implements Runnable {     //用于统计时间
        private long start ;
        private String lockType;

        public Time(String lockType, long start) {
            this.start = start;
            this.lockType = lockType;
        }

        public void run() {
            System.out.println(lockType + "耗时:" + String.valueOf(System.currentTimeMillis() - start));
        }
    }
}

公平锁结果:

运行结果:总体的线程锁的获取上基本上是公平的。

代码语言:javascript
复制
0获取锁的当前线程[1], 同步队列中的线程[]
0获取锁的当前线程[0], 同步队列中的线程[4, 3, 2, 1]
0获取锁的当前线程[4], 同步队列中的线程[3, 2, 1, 0]
0获取锁的当前线程[3], 同步队列中的线程[2, 1, 0, 4]
0获取锁的当前线程[2], 同步队列中的线程[1, 0, 4, 3]
1获取锁的当前线程[1], 同步队列中的线程[0, 4, 3, 2]
1获取锁的当前线程[0], 同步队列中的线程[4, 3, 2]
1获取锁的当前线程[4], 同步队列中的线程[3, 2]
1获取锁的当前线程[3], 同步队列中的线程[2]
1获取锁的当前线程[2], 同步队列中的线程[]
公平锁耗时:9

非公平锁结果:

只需将ReentrantLock构造中的true去掉即是非公平锁。运行结果如下:

代码语言:javascript
复制
0获取锁的当前线程[0], 同步队列中的线程[]
1获取锁的当前线程[0], 同步队列中的线程[]
0获取锁的当前线程[2], 同步队列中的线程[]
1获取锁的当前线程[2], 同步队列中的线程[]
0获取锁的当前线程[3], 同步队列中的线程[]
1获取锁的当前线程[3], 同步队列中的线程[]
0获取锁的当前线程[4], 同步队列中的线程[]
1获取锁的当前线程[4], 同步队列中的线程[]
0获取锁的当前线程[1], 同步队列中的线程[]
1获取锁的当前线程[1], 同步队列中的线程[]
非公平锁耗时:7

线程会重复获取锁。如果申请获取锁的线程足够多,那么可能会造成某些线程长时间得不到锁。

总结

公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象

公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

参考文章

https://juejin.im/post/5aeb0a8b518825673a2066f0 https://www.cnblogs.com/yulinfeng/p/6899316.html https://www.cnblogs.com/leesf456/p/5383609.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱编码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 核心概念
    • 1. 重入性
      • 2. 公平锁和非公平锁
      • 源码分析
        • 1 类的继承关系 
          • 2 类的内部类
            • 2.1 Sync类
            • 2.2. NonfairSync类
            • 2.3. FairSyn类
        • 案例
          • 公平锁结果:
            • 非公平锁结果:
            • 总结
            • 参考文章
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档