Java中的ReentrantLock解读

 更新时间:2023年09月27日 10:26:39   作者:WYSCODER  
这篇文章主要介绍了Java中的ReentrantLock解读,ReentantLock是java中重入锁的实现,一次只能有一个线程来持有锁,包含三个内部类,Sync、NonFairSync、FairSync,需要的朋友可以参考下

ReentrantLock

ReentantLock 是java中重入锁的实现,一次只能有一个线程来持有锁,包含三个内部类, Sync 、 NonFairSync 、 FairSync 。

1、构造函数

无参构造,默认使用的是非公平性锁

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

有参构造, Boolean类型的参数 true:表示公平性锁 false:非公平性锁

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

reentantlock是lock接口的实现类,即实现了Lock接口下所有的方法; 获取锁的方法lock、trylock、lockintertuptibly加锁方式以及释放锁方法。

2、公平性锁和非公平性锁

(1)公平性锁和非公平性锁示例

NonFairAndFairDemo类

import java.util.concurrent.locks.ReentrantLock;
public class NonFairAndFairDemo implements Runnable {
    //静态变量(线程共享)
    private static int num = 0;
    //锁实例
    private ReentrantLock rtl;
    public NonFairAndFairDemo(ReentrantLock rtl) {
        this.rtl = rtl;
    }
    @Override
    public void run() {
        while (true) {
            //加锁
            rtl.lock();
            num++;
            System.out.println(Thread.currentThread().getName() + ":" + num);
            rtl.unlock();
        }
    }
}

测试公平锁

@Test
public void test01() {
       ReentrantLock reentrantLock = new ReentrantLock(true);
       Thread threadA = new Thread(new NonFairAndFairDemo(reentrantLock));
       threadA.setName("A");
       Thread threadB = new Thread(new NonFairAndFairDemo(reentrantLock));
       threadB.setName("B");
       threadA.start();
       threadB.start();
}

执行结果

在这里插入图片描述

公平向锁特征如上:按照线程的访问顺序进行获取。

测试非公平锁

@Test
public void test02() {
    ReentrantLock reentrantLock = new ReentrantLock(false);
    Thread threadA = new Thread(new NonFairAndFairDemo(reentrantLock));
    threadA.setName("A");
    Thread threadB = new Thread(new NonFairAndFairDemo(reentrantLock));
    threadB.setName("B");
    threadA.start();
    threadB.start();
}

执行结果

在这里插入图片描述

非公平性锁的特点,是每个线程都连续执行多次之后在替换成其他线程执行。

(2)公平锁和非公平锁的实现

abstract static class Sync extends AbstractQueuedSynchronizer

公平性锁和非公平性锁的父类是 sync , sync 类是 AbstractQueuedSynchronizer 是其子类,AQS是一个同步器,提供同步功能。

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
        //加锁操作,声明是抽象方法,nofairsync和fairsync中各自实现
        abstract void lock();
        //非公平获取,公平性锁和非公平性锁都需要这个方法
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //AQS获取state值 
            int c = getState();
            if (c == 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");
                //当前线程继续持有锁,仅对state进行加操作
                setState(nextc);
                return true;
            }
            return false;
        }
        //释放锁  sync中的tryRelease是公平性锁和非公平性锁的释放锁流程都是该方法
        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;
        }
        //是否上锁  true:表示加锁
        final boolean isLocked() {
            return getState() != 0;
        }
}

该Sync中方法的封装是调用AQS中的方法实现的

公平性锁和非公平锁的如何实现?

公平性锁:FairLock

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        final void lock() {
            acquire(1);
        }
        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)
                    //有符号的int类型。最高位为1表示负数
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
类AbstractQueuedSynchronizer#acquire
    public final void acquire(int arg) {
    //当前同步状态非空闲,并且是其他线程持有锁 返回false
        if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

公平性锁获取锁流程:

1、如果同步状态为空,就可以抢锁,能够获取锁的前提条件是当前等待队列为空,或者等待队列队头是当前线程,即当前线程才能够抢锁,通过CAS抢锁(state)抢锁成功记录当前线程信息到锁上。

2、如果同步状态不为空,即存在线程占用锁且占用线程是当前线程,当前线程可成功获取锁(state)。

非公平性锁

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        final void lock() {
            //执行Lock操作,尝试立即获取锁,失败就退回常规流程
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);//立即获取锁失败进入到acquire,首先调用tryAcquire
        }
        protected final boolean tryAcquire(int acquires) {
            //同步状态为空闲或者不为空闲但是是当前线程持有锁,返回true表示抢锁成功
            return nonfairTryAcquire(acquires);
        }
    }
类AbstractQueuedSynchronizer#acquire
    public final void acquire(int arg) {
    //当前同步状态非空闲,并且是其他线程持有锁 返回false
        if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

通过代码可知:很多方法,trylock,unlock都是在父类sync实现

非公平性锁抢锁流程:

1、直接通过CAS操作抢锁,如果不成功进入常规抢锁流程。

2、获取当前锁的状态(state是否为0),如果为0表示空闲,直接通过CAS抢锁,如果成功,记录线程信息到锁上。

3、如果锁不为空闲且是当前线程持有锁,则可直接获取锁(state+1)。

重入锁的实现: ReentrantLock 都是将具体实现委托给内部类(Sync、NonFairSync、FairSync)。 ReentrantLock 的重入次数是使用AQS的state属性,state大于0表示锁被占用(值表示当前线程重入次数),等于0表示锁空闲,小于0则表示重入次数太多导致溢出了。 可重入锁需要一个重入计数的变量,初始值为0,当成功请求锁加1,释放锁时减1,当释放锁时计数为0则真正释放锁,重入锁必须持有对锁持有者的引用,用以判断是否可以重入。

(3)Condition

synchronized与wait、notify、notifyAll方法结合可以实现等待/通知模式。reentantlock同样可以实现等待、通知模式,需要借助于Condition对象,具有更好的灵活性。

newCondition方法

public Condition newCondition()

Condition中提供的方法如下:

在这里插入图片描述

awaitXXX和Object中的wait方法类似,使当前线程进入休眠等待, signal和Object中的notify方法类似,唤醒一个处于休眠状态的线程 signalAll和Object中的signalAll方法类似,唤醒所有处于休眠状态的线程。

生产者和消费者

生产者

public class Producer extends Thread {
    private LinkedList<Integer> cap;//共享仓库
    private Random random = new Random();
    private ReentrantLock rlk;
    private Condition pToc;
    private Condition cTop;
    public Producer(LinkedList<Integer> cap, ReentrantLock rlk, Condition pToc, Condition cTop) {
        this.cap = cap;
        this.rlk = rlk;
        this.pToc = pToc;
        this.cTop = cTop;
    }
    @Override
    public void run() {
        while (true) {
            rlk.lock();
            try {
                if (cap.size() == 3) {//缓冲区满 生产者进行阻塞
                    System.out.println("缓冲区满");
                    cTop.await();
                }
                //生产产品
                int i = random.nextInt(1000);
                System.out.println("生产者生产了" + i);
                cap.add(i);
                //通知消费者消费产品
                pToc.signal();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            rlk.unlock();
        }
    }
}

消费者

public class Consumer extends Thread {
    private LinkedList<Integer> cap;//共享仓库
    private ReentrantLock rlk;
    private Condition pToc;
    private Condition cTop;
    public Consumer(LinkedList<Integer> cap, ReentrantLock rlk, Condition pToc, Condition cTop) {
        this.cap = cap;
        this.rlk = rlk;
        this.pToc = pToc;
        this.cTop = cTop;
    }
    @Override
    public void run() {
        while (true) {
            rlk.lock();
            try {
                if (cap.size() == 0) { //如果缓冲区为0,消费者阻塞
                    System.out.println("缓冲区为空");
                    pToc.await();
                }
                //消费者消费产品
                Integer i = cap.remove();
                System.out.println("消费者消费了" + i);
                //通知生产者生产
                cTop.signal();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            rlk.unlock();
        }
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        LinkedList<Integer> cap = new LinkedList<>();
        ReentrantLock reentrantLock = new ReentrantLock();
        //生产者通知消费者
        Condition pToc = reentrantLock.newCondition();
        //消费者通知生产者
        Condition cTop = reentrantLock.newCondition();
        Producer producer = new Producer(cap,reentrantLock,pToc,cTop);
        Consumer consumer = new Consumer(cap,reentrantLock,pToc,cTop);
        producer.start();
        consumer.start();
    }
}

执行结果

在这里插入图片描述

  • 在调用Condition中的await或者是signal这些方法中任何的方法时,必须持有锁(ReentantLock),如果没有持有此锁,则抛出IllegalMonitorStateException异常。
  • 在调用await方法时,将释放掉锁,并在这些方法返回之前,重新先获取该锁,才能执行。
  • 如果线程在等待中被中断,则等待将终止,并抛出InterruptedException,清除掉中断状态。
  • 等待状态的线程按照FIFO顺序接收信号。
  • 等待方法返回的线程重新获取锁的顺序与线程最初获取锁的顺序是相同的。

循环打印ABC

ABCThread类

public class ABCThread extends Thread {
    private String name;
    private ReentrantLock rtl;
    private Condition waitc;//等待Condition
    private Condition sigalc; //通知Condition
    public ABCThread(String name,ReentrantLock rtl,Condition wc,Condition sc){
        this.name = name;
        this.rtl = rtl;
        this.waitc = wc;
        this.sigalc = sc;
    }
    @Override
    public void run() {
        int num =0;
        while (true) {
            rtl.lock();
            //等待其他线程通知,
            try {
                waitc.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //打印当前线程信息
            System.out.println(name);
            //通知下一个线程
            sigalc.signal();
            ++num;
            if (num >= 10) break;
            rtl.unlock();
        }
    }
}

测试

@Test
public void test() {
    ReentrantLock reentrantLock = new ReentrantLock();
    //A通知B
    Condition ab = reentrantLock.newCondition();
    //B通知C
    Condition bc = reentrantLock.newCondition();
    //C通知A
    Condition ca = reentrantLock.newCondition();
    new ABCThread("A", reentrantLock, ca, ab).start();
    new ABCThread("B", reentrantLock, ab, bc).start();
    new ABCThread("C", reentrantLock, bc, ca).start();
    //先发起通知A线程
    reentrantLock.lock();
    ca.signal();
    reentrantLock.unlock();
}

执行结果

在这里插入图片描述

到此这篇关于Java中的ReentrantLock解读的文章就介绍到这了,更多相关Java的ReentrantLock内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot服务上实现接口限流的方法

    SpringBoot服务上实现接口限流的方法

    这篇文章主要介绍了SpringBoot服务上实现接口限流的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • mybatis拦截器及不生效的解决方法

    mybatis拦截器及不生效的解决方法

    本文主要介绍了mybatis拦截器及不生效的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • 如何基于java或js获取URL返回状态码

    如何基于java或js获取URL返回状态码

    这篇文章主要介绍了如何基于java或js获取URL返回状态码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • java读取http请求中的body实例代码

    java读取http请求中的body实例代码

    下面小编就为大家带来一篇java读取http请求中的body实例代码。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-09-09
  • 深入浅析Spring 中的Null-Safety

    深入浅析Spring 中的Null-Safety

    Spring Framework 本身利用了上面这几个注释,但它们也可以运用在任何基于Spring的Java 项目中,以声明空安全api 和 空安全字段。这篇文章主要介绍了Spring 中的Null-Safety相关知识 ,需要的朋友可以参考下
    2019-06-06
  • Java高效利用异常处理的技巧总结

    Java高效利用异常处理的技巧总结

    这篇文章主要为大家详细介绍了Java如何高效利用异常处理,从而达到优化代码的效果,文中的示例代码讲解详细,感兴趣的小伙伴可以学习一下
    2023-09-09
  • Java实现排队论的原理

    Java实现排队论的原理

    这篇文章主要为大家详细介绍了Java实现排队论的原理,对排队论感兴趣的小伙伴们可以参考一下
    2016-02-02
  • Spring Bean生命周期源码原理图解

    Spring Bean生命周期源码原理图解

    这篇文章主要介绍了Spring Bean生命周期源码原理图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • MyBatis批量添加数据2种实现方法

    MyBatis批量添加数据2种实现方法

    这篇文章主要介绍了MyBatis批量添加数据2种实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • spring-data-redis 动态切换数据源的方法

    spring-data-redis 动态切换数据源的方法

    最近遇到了一个麻烦的需求,我们需要一个微服务应用同时访问两个不同的 Redis 集群,一般情况下我们会怎么处理呢,下面通过场景分析给大家介绍spring-data-redis 动态切换数据源的方法,感兴趣的朋友一起看看吧
    2021-08-08

最新评论