JUC之Lock、ReentrantLock可重入独占锁

前言

  ReentrantLock即可重入锁,实现了Lock和Serializable接口

  在java环境下ReentrantLock和Synchronized都是可重入锁

  ReentrantLock构造函数中提供两种锁:创建公平锁和非公平锁(默认)

  • ReentrantLock有三个内部类 Sync、NonfairSync和FairSync类。
  • Sync继承AbstractQueuedSynchronized抽象类
  • NonfairSync(非公平锁)继承Sync抽象类。
  • FairSync(公平锁)继承Sync抽象类。

  公平锁,线程按照发出的请求的顺序获取锁;非公平锁,允许插队。

  公平锁上,若有另外一个线程持有锁或有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到对类中。

  非公平锁性能高于公平锁性能的原因

  • 在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟
  • 假设线程 A 持有一个锁,并且线程 B 请求这个锁。由于锁被 A 持有,因此 B 将被挂起。当 A 释放锁时,B 将被唤醒,因此 B 会再次尝试获取这个锁。与此同时,如果线程 C 也请求这个锁,那么 C 很可能会在 B 被完全唤醒之前获得、使用以及释放这个锁。这样就是一种双赢的局面,B 获得锁的时刻并没有推迟,C 更早的获得了锁,并且吞吐量提高了。

  当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁

公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。

源码

ReentrantLock类是实现了Lock接口,先看看接口里的抽象方法

Lock API

void lock();  //获取锁
void lockInterruptibly() throws InterruptedException;  //如果当前线程未被中断,则获取锁
boolean tryLock();  //只有锁在空闲状态时才能获取该锁。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;  //锁在给定时间内空闲,且当前线程未被中断,则获取锁
void unlock();  //释放锁
Condition newCondition();  //返回绑定到此Lock实例的新 Condition 实例。

Lock接口中出现的Condition接口,它是什么作用呢?

Condition

  wait()、notify()和synchronized配合可以实现等待通知,Condition和Lock配合同样也可以实现等待通知,但是两者之间还是有区别的

Condition定义了等待/通知两种类型的方法,当前线程调用这些方法,需要提前获取到Condition对象关联的锁,Condition对象是由Lock对象创建出来的(Lock.newCondition),换句话说,Condition是依赖Lock对象的。

ConditionAPI

public interface Condition {
    void await() throws InterruptedException;  //当前线程进入等待状态直到被通知(signal)或被中断
    void awaitUninterruptibly();  //不响应中断等待,直到被通知(signal)
    long awaitNanos(long nanosTimeout) throws InterruptedException;  //等待指定时长直到被通知或中断或超时
    boolean await(long time, TimeUnit unit) throws InterruptedException;  //同上
    boolean awaitUntil(Date deadline) throws InterruptedException; //当前线程进入等待状态直到被通知、中断或者到某个事件。若没有到指定事件就被通知,方法返回true,否则false。
    void signal();  //唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁。
    void signalAll();  //唤醒所有等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关的锁
}

ReentrantLock源码

构造器

public ReentrantLock() {sync = new NonfairSync();}  //默认使用非公平锁对象
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}  //根据参数,判断使用 非公平/公平 锁。

内部类

  构造器中,是创建内部类的对象NonfairSync(非公平锁)和FairSync(公平锁),它们都实现了Sync静态内部类

  Sync静态内部类源码

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) {  //如果为0,当前锁未被其他线程获取
                if (compareAndSetState(0, acquires)) {  //通过CAS设置
                    setExclusiveOwnerThread(current); //设置获取锁的此线程
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {  //如果锁状态不为0,则已经被其他线程获取。如果是当前线程,也就是同一个线程再次获取这个锁,即可重入
                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) {  //状态值为0,释放锁
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
     //检查锁是否为此线程的所有者。就是之前是否是这个线程获取的锁
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
     //创建ConditionObject对象
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
     //返回该锁的线程
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
     //返回锁的状态值。如果是该锁的线程,返回对应的值;不是返回0
        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
        }
    }

NonfairSync非公平锁的源码实现

  非公平锁就是一种获取锁的抢占机制,是 随机获得锁 的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了.

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;      //执行锁
        final void lock() {
            if (compareAndSetState(0, 1)) //通过CAS操作,设置状态值
                setExclusiveOwnerThread(Thread.currentThread());  //设置该锁的线程
            else
                acquire(1);
        }      //尝试获取锁。默认是非公平锁,转至Sync中的方法
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

FairSync公平锁的源码实现

  公平锁表示线程获取锁顺序是按照 线程加锁的顺序 来分配的,即先来先得的 FIFO 先进先出顺序。

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) { //等于0,该锁未被线程获取
                if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {  //hasQueuedPredecessors()方法判断头节点是否为当前线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {  //如果不等于0,判断该锁的线程是否为当前线程,是可重入。
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

方法

//获得锁
public void lock() {
    sync.lock();
}//当前线程未被中断,获取锁
public void lockInterruptibly() throws InterruptedException {
   sync.acquireInterruptibly(1);
}
//该锁未被其他线程获取,则当前线程获取锁。默认非公平锁
public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
//给定时间内,该锁未被其他线程获取,该线程未被中断,可获取锁
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
   return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
//释放锁
public void unlock() {
   sync.release(1);
}
//返回Condition对象
public Condition newCondition() {
    return sync.newCondition();
}
//返回同步状态
public int getHoldCount() {
   return sync.getHoldCount();
}
//锁的持有者是否为当前线程
public boolean isHeldByCurrentThread() {
    return sync.isHeldExclusively();
}
//该锁是否已被线程获取
public boolean isLocked() {
    return sync.isLocked();
}
//判断是否为公平锁
public final boolean isFair() {
    return sync instanceof FairSync;
}
//返回拥有该锁的线程
protected Thread getOwner() {
    return sync.getOwner();
}
//查询是否有线程正在等待获取此锁
public final boolean hasQueuedThreads() {
    return sync.hasQueuedThreads(); //这个方法就是判断 头节点==尾结点? 没有等待线程:有等待线程
}
//判断线程是否在等待线程队列中
public final boolean hasQueuedThread(Thread thread) {
    return sync.isQueued(thread);
}
//返回等待线程数
public final int getQueueLength() {
    return sync.getQueueLength();
}
//返回线程队列
protected Collection<Thread> getQueuedThreads() {
    return sync.getQueuedThreads();
}
//查询是否有线程正在等待与此锁关联的给定条件。
public boolean hasWaiters(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}
//返回与此锁关联的给定条件下等待的线程数的估计值。
public int getWaitQueueLength(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}
//返回一个集合,其中包含可能正在等待与此锁关联的给定条件的线程。
protected Collection<Thread> getWaitingThreads(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}

总结

  • ReentrantLock 提供了内置锁(synchronized)类似的功能和内存语义。
  • ReentrantLock 提供了包括定时的锁等待、可中断的锁等待、公平性以及实现非块结构的加锁。
  • Condition 对线程的等待和唤醒等操作更加灵活,一个 ReentrantLock 可以有多个 Condition 实例,更有扩展性。
  • ReentrantLock 需要显示的获取锁,并在 finally 中释放锁。
  • JDK 1.8 以前 ReentrantLock 在性能上似乎优于 synchronized,但获取锁的操作不能与特定的栈帧关联起来,而内置锁可以。
  • 因为内置锁时 JVM 的内置属性,所以未来更可能提升 synchronized 而不是 ReentrantLock 的性能。
    • 例如对线程封闭的锁对象消除优化,通过增加锁粒度来消除内置锁的同步。

参考:https://www.jianshu.com/p/54e90999ee47

原文地址:https://www.cnblogs.com/FondWang/p/12112234.html

时间: 2024-10-13 04:02:26

JUC之Lock、ReentrantLock可重入独占锁的相关文章

聊聊高并发(二十七)解析java.util.concurrent各个组件(九) 理解ReentrantLock可重入锁

这篇讲讲ReentrantLock可重入锁,JUC里提供的可重入锁是基于AQS实现的阻塞式可重入锁.这篇 聊聊高并发(十六)实现一个简单的可重入锁 模拟了可重入锁的实现.可重入锁的特点是: 1. 是互斥锁,基于AQS的互斥模式实现,也就是说同时只有一个线程进入临界区,唤醒下一个线程时也只能释放一个等待线程 2. 可重入,通过设置了一个字段exclusiveOwnerThread来标示当前获得锁的线程.获取锁操作是,如果当前线程是已经获得锁的线程,那么获取操作成功.把当前状态作为获得锁次数的计数器

ReentrantLock可重入锁的使用场景(转)

摘要 从使用场景的角度出发来介绍对ReentrantLock的使用,相对来说容易理解一些. 场景1:如果发现该操作已经在执行中则不再执行(有状态执行) a.用在定时任务时,如果任务执行时间可能超过下次计划执行时间,确保该有状态任务只有一个正在执行,忽略重复触发.b.用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行(忽略重复触发). 以上两种情况多用于进行非重要任务防止重复执行,(如:清除无用临时文件,检查某些资源的可用性,数据备份操作等) ? 1 private Reent

ReentrantLock可重入锁的使用场景

摘要 从使用场景的角度出发来介绍对ReentrantLock的使用,相对来说容易理解一些. 场景1:如果发现该操作已经在执行中则不再执行(有状态执行) a.用在定时任务时,如果任务执行时间可能超过下次计划执行时间,确保该有状态任务只有一个正在执行,忽略重复触发.b.用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行(忽略重复触发). 以上两种情况多用于进行非重要任务防止重复执行,(如:清除无用临时文件,检查某些资源的可用性,数据备份操作等) ? 1 private Reent

java ReentrantLock可重入锁的使用场景

摘要 从使用场景的角度出发来介绍对ReentrantLock的使用,相对来说容易理解一些. 场景1:如果发现该操作已经在执行中则不再执行(有状态执行) a.用在定时任务时,如果任务执行时间可能超过下次计划执行时间,确保该有状态任务只有一个正在执行,忽略重复触发.b.用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行(忽略重复触发). 以上两种情况多用于进行非重要任务防止重复执行,(如:清除无用临时文件,检查某些资源的可用性,数据备份操作等) [java] view plain

ReenTrantLock可重入锁(和synchronized的区别)总结

可重入性: 从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入的,两者关于这个的区别不大.两者都是同一个线程没进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁. 锁的实现: Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别.前者的实现是比较难见到的,后者有直接的源码可供阅读. 性能的区别: 在S

40 多线程(十二)——ReentrantLock 可重入锁

我们使用的synchronized加的锁是可以延续使用的,如下: public void test() { //第一次获得锁 synchronized(this) { while(true) { //第二次获得同样的锁 synchronized(this) { System.out.println("ReentrantLock"); } try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-g

并发编程(4):锁重入与锁异常

1.synchronized重入 关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到了一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁. 1.1 同步方法间的调用 public class Demo5 { public synchronized void method1() throws InterruptedException { System.out.println("method..."); method2(); Thr

可重入的独占锁——ReentrantLock源码分析

ReentrantLock面试题分析 1.ReentrantLock是怎么实现的? 2.ReentrantLock的公平锁和非公平锁是如何实现的? 1.ReentrantLock类图结构 从类图我们可以直观地了解到,ReentrantLock最终还是使用AQS来实现地,并且根据参数来决定其内部是一个公平??还是非公平锁??,默认是非公平锁??. public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(bo

Java多线程之JUC包:ReentrantLock源码学习笔记

若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5627539.html ReentrantLock是JUC包提供的一种可重入独占锁,它实现了Lock接口.与Semaphore类似,ReentrantLock也提供了两种工作模式:公平模式&非公平模式,也是通过自定义两种同步器FairSync&NonfairSync来实现的. 源代码: /* * ORACLE PROPRIETARY/CONF