6

深入理解独占锁ReentrantLock类锁 - 忧愁的chafry

 2 years ago
source link: https://www.cnblogs.com/chafry/p/16754842.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

 ReentrantLock介绍

【1】ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

【2】相对于 synchronized, ReentrantLock具备如下特点:

1)可中断 
2)可以设置超时时间
3)可以设置为公平锁 
4)支持多个条件变量
5)与 synchronized 一样,都支持可重入

ReentrantLock问题分析

【1】ReentrantLock公平锁和非公平锁的性能谁更高?

  1)那肯定是非公平锁,但是为什么是非公平更高呢?

  2)因为涉及到了线程的park()与unpark()操作,不管是ReentrantLock还是synchronized,都在避免这些操作。

    (1)如ReentrantLock的非公平同步器在得不到锁的情况下,即将要进入之前会再加一次锁,生成节点之后又会加一次锁,把节点放入队列之后又会加一次锁,最终迫不得已才会进行park()操作。

    (2)如synchronized,在生成monitor的过程之中也会多次尝试加锁,避免monitor的生成。

  3)为什么要避免呢?这就涉及到线程的概念了。

    (1)因为park()与unpark()操作涉及到了线程的上下文切换,同时又涉及到了时间片轮转机制

    (2)线程上下文切换,需要将旧的线程资源保存回内存【保存执行到了哪一步,需要什么东西】,将新的线程的资源加载入CPU,让新线程具备执行的资源并开始执行。但是这些操作都是需要花费时间的,会消耗一部分时间片的资源。如(这里仅仅只是举例说明),一个时间片本来就是50s,你拿到的时候花费了一定的时间(如10s)进行上下文切换,现在刚执行不到5s,你又要进行一次切换(又要花费10s)。那下一个拿到时间片的线程会不会还是会继续切换呢?而且你要下次运行就又要等时间片了。

    (3)所以说,本质上非公平机制是为了让持有CPU的线程尽可能多的做有用的任务,减少上线下文切换带来的开销,毕竟时间片来之不易,本身就是从众多线程之中好不容易分配得来的。

ReentrantLock的使用

【1】使用模板

Lock lock = new ReentrantLock();
//加锁
lock.lock();
try {
    // 临界区代码
    // TODO 业务逻辑:读写操作不能保证线程安全
} finally {
    // 解锁,放置在这里的原因是保证异常情况都不能干扰到解锁逻辑
    lock.unlock();
}

【2】可重入的尝试

public class ReentrantLockDemo {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }
    public static void method1() {
        lock.lock();
        try {
            log.debug("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }
    public static void method2() {
        lock.lock();
        try {
            log.debug("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }
    public static void method3() {
        lock.lock();
        try {
            log.debug("execute method3");
        } finally {
            lock.unlock();
        }
    }
}

【3】中断机制尝试

  进行说明:这里面其实是main线程先获得了锁,所以t1线程其实是先进入队列里面,然后在main线程里面将t1设置为了中断。当main线程释放锁的时候,t1去加锁,发现自己被中断了,所以抛出中断异常,退出加锁。【其实这个中断加锁,怎么说,就是可以让失去加锁的权利,但是不影响你去排队】

@Slf4j
public class ReentrantLockDemo {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();

        Thread t1 = new Thread(() -> {

            log.debug("t1启动...");

            try {
                lock.lockInterruptibly();
                try {
                    log.debug("t1获得了锁");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("t1等锁的过程中被中断");
            }

        }, "t1");
        
        lock.lock();
        try {
            log.debug("main线程获得了锁");
            t1.start();
            //先让线程t1执行
            Thread.sleep(10000);

            t1.interrupt();
            log.debug("线程t1执行中断");
        } finally {
            lock.unlock();
        }

    }

}

【4】锁超时尝试

@Slf4j
public class ReentrantLockDemo {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();

        Thread t1 = new Thread(() -> {

            log.debug("t1启动...");
            //超时
            try {
                // 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("等待 1s 后获取锁失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }

            try {
                log.debug("t1获得了锁");
            } finally {
                lock.unlock();
            }

        }, "t1");


        lock.lock();
        try {
            log.debug("main线程获得了锁");
            t1.start();
            //先让线程t1执行
            Thread.sleep(2000);
        } finally {
            lock.unlock();
        }

    }

}

【5】条件变量的尝试

    1)java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或 Condition.signalAll() 方法唤醒等待的线程。

    2)由于可控的原因我们甚至可以多个条件队列来进行对线程调控。

  注意:调用Condition的await()和signal()方法,都必须在lock保护之内

@Slf4j
public class ReentrantLockDemo {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition cigCon = lock.newCondition();
    private static Condition takeCon = lock.newCondition();

    private static boolean hashcig = false;
    private static boolean hastakeout = false;

    //送烟
    public void cigratee(){
        lock.lock();
        try {
            while(!hashcig){
                try {
                    log.debug("没有烟,歇一会");
                    cigCon.await();

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            log.debug("有烟了,干活");
        }finally {
            lock.unlock();
        }
    }

    //送外卖
    public void takeout(){
        lock.lock();
        try {
            while(!hastakeout){
                try {
                    log.debug("没有饭,歇一会");
                    takeCon.await();

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            log.debug("有饭了,干活");
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockDemo6 test = new ReentrantLockDemo6();
        new Thread(() ->{
            test.cigratee();
        }).start();

        new Thread(() -> {
            test.takeout();
        }).start();

        new Thread(() ->{
            lock.lock();
            try {
                hashcig = true;
                log.debug("唤醒送烟的等待线程");
                cigCon.signal();
            }finally {
                lock.unlock();
            }


        },"t1").start();

        new Thread(() ->{
            lock.lock();
            try {
                hastakeout = true;
                log.debug("唤醒送饭的等待线程");
                takeCon.signal();
            }finally {
                lock.unlock();
            }


        },"t2").start();
    }

}

ReentrantLock源码分析(版本为jdk14)

【0】前置部分最好有关于JDK实现管程的了解【可查看  深入理解AQS--jdk层面管程实现

【1】ReentrantLock类自身部分

  0)继承关系

//锁的接口定义,定义了一个锁该具备哪一些功能
public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}

  1)属性值

//同步器句柄,同步器提供了所有的实现机制
private final Sync sync;

  2)构造方法

//默认是采用非公平的同步器
public ReentrantLock() {
    sync = new NonfairSync();
}

//此外可以根据传入的参数选择同步器
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

  3)其他方法【看完之后,你会发现,其实ReentrantLock什么事都不干,统统都交给了持有的AQS同步器去干活了,有一种修饰器设计模式的味道,只是包装了一下,具体内部的同步器类型由自己选择,所以同步器显得就很重要

public class ReentrantLock implements Lock, java.io.Serializable {
    .....
    //获取锁定
    public void lock() {
        sync.lock();
    }

    //中断式的加锁,如果没有被中断就会加锁
    public void lockInterruptibly() throws InterruptedException {
        sync.lockInterruptibly();
    }

    //仅当在调用时锁不被另一个线程持有时才获取锁
    public boolean tryLock() {
        return sync.tryLock();
    }
   //超时加锁,限定加锁时间
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryLockNanos(unit.toNanos(timeout));
    }

    //尝试释放此锁
    public void unlock() {
        sync.release(1);
    }

    //返回一个用于此锁定实例的条件实例,说白了就是监视器
    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);
    }


}

【2】抽象的Sync类部分

abstract static class Sync extends AbstractQueuedSynchronizer {
    //定义了核心的加锁逻辑
    @ReservedStackAccess
    final boolean tryLock() {
        Thread current = Thread.currentThread();
        //获取State属性值,这是在AQS里面定义的值,用于标记是否可以加锁,0代表没有人在用锁,1代表有人在占用,大于1说明这个锁被这个人加了多次【即重入锁概念】
        int c = getState();
        if (c == 0) {
            //CAS保证只有一个人能成功
            if (compareAndSetState(0, 1)) {
                //设置持有锁的线程
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (getExclusiveOwnerThread() == current) { //走到这里说明有人持有了锁,但是可以判断持有的人是不是自己【可重入】
            if (++c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            //因为每一次重入都会导致State的值+1,所以解锁的时候对应要减1
            setState(c);
            return true;
        }
        return false;
    }

    //为子类留下的加锁逻辑的抽象方法
    abstract boolean initialTryLock();

    //核心加锁逻辑里面便是使用抽象方法进行加锁
    @ReservedStackAccess
    final void lock() {
        if (!initialTryLock())
            acquire(1);
    }

    @ReservedStackAccess
    final void lockInterruptibly() throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!initialTryLock())
            acquireInterruptibly(1);
    }

    @ReservedStackAccess
    final boolean tryLockNanos(long nanos) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return initialTryLock() || tryAcquireNanos(1, nanos);
    }

    //尝试释放锁
    @ReservedStackAccess
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (getExclusiveOwnerThread() != Thread.currentThread())
            throw new IllegalMonitorStateException();
        boolean free = (c == 0);
        if (free)
            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
    }
}

【3】实现抽象的 Sync类 的公平锁 FairSync类部分

static final class FairSync extends Sync {
    //尝试锁定方法
    final boolean initialTryLock() {
        Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //看得出来首先队列要为空,其次才是CAS加锁成功,才算能够持有锁 
            //也就是说队列不为空,连CAS加锁的资格都没有,所以十分公平
            if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (getExclusiveOwnerThread() == current) {
            if (++c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        }
        return false;
    }

    //尝试获取方法
    protected final boolean tryAcquire(int acquires) {
        if (getState() == 0 && !hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}

//AbstractQueuedSynchronizer类#hasQueuedThreads方法
//判断队列是否为空【由于AQS里面采用的是链表实现队列效果,所以是判断节点情况】
public final boolean hasQueuedThreads() {
    for (Node p = tail, h = head; p != h && p != null; p = p.prev)
        if (p.status >= 0)
            return true;
    return false;
}

【4】实现抽象的 Sync类 的非公平锁 NonfairSync类部分

//与公平的同步器进行比较的话,会发现,他们本质没什么区别,因为大多数走的都是抽象方法的逻辑和AQS的方法
//最大的区别在于加锁的方式不同,公平方式,队列没人才去加锁;非公平方式,不管队列有没有人,都是直接去加锁,加到了就持有
static final class NonfairSync extends Sync {
    //尝试锁定方法
    final boolean initialTryLock() {
        Thread current = Thread.currentThread();
        //直接尝试CAS获取加锁权利
        if (compareAndSetState(0, 1)) { // first attempt is unguarded
            setExclusiveOwnerThread(current);
            return true;
        } else if (getExclusiveOwnerThread() == current) {
            int c = getState() + 1;
            if (c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        } else
            return false;
    }

    //尝试获取方法
    protected final boolean tryAcquire(int acquires) {
        //判断是否有人持有锁,没有则去加锁
        if (getState() == 0 && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}

Recommend

  • 35

    ReentrantLock 是 Java 在 JDK1.5 引入的显式锁,在实现原理和功能上都和内置锁(synchronized)上都有...

  • 4

    顾老侨居新西兰随笔(九): 世外桃源的安乐与忧愁 Jack Liu 8/Dec/12 阅读(17,916) 评论(0)...

  • 6

    取消大小周后,那些忧愁的大厂年轻人 Ella 2021-08-30 4 评论...

  • 6

    视频:网红小家电的“忧愁” | GPLP犀牛财经买了那么多网红小家电,幸福感提高了吗? Hello,大家好,我是犀牛君。 在外漂泊,努力工作多年,买了一堆试图节省时间以及提升幸福感的网红小家电。例如三明治机、榨汁杯、...

  • 8

    出走的“三千烦恼丝”,难解的秃发忧愁 2022-02-10 10:55:05  作者:南哥 来源:柒财经  在国内,2.5亿脱发人口中,90...

  • 2

    为什么需要分布式锁   1.为了解决Java共享内存模型带来的线程安全问题,我们可以通过加锁来保证资源访问的单一,如JVM内置锁synchronized,类级别的锁ReentrantLock。   2.但是随着业务的发展,单机服务毕竟存在着限制,故会往多台组合形成集群...

  • 8
    • www.cnblogs.com 2 years ago
    • Cache

    Redis详解 - 忧愁的chafry

    Redis介绍     1.Redis 是一个基于内存的高性能 key-value 数据库。是完全开源免费的,用C语言编写的,遵守BSD协议     2.Redis 特点:       1)Redis 是基于内存操作的,吞吐量非常高,可以在 1s内完成十万次读写操作...

  • 8

    Redis 基本特性   1. 非关系型的键值对数据库,可以根据键以O(1) 的时间复杂度取出或插入关联值  2. Redis 的数据是存在内存中的  3. 键值对中键的类型可以是字符串,整型,浮点型等,且键是唯一的  4. 键值对中的值类型可以是st...

  • 1
    • www.cnblogs.com 2 years ago
    • Cache

    Dubbo2.7源码详解 - 忧愁的chafry

    Spring与Dubbo整合原理与源码分析   【1】注解@EnableDubbo @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @EnableDubboConfig // @EnableDubboConfig注解用来将properties文件中的配置项转...

  • 1

    ReentrantLock和Synchronized都是Java开发中最常用的锁,与Synchronized这种JVM内置锁不同的是,ReentrantLock提供了更丰富的语义。可以创建公平锁或非公平锁、响应中断、超时等待、按条件唤醒等。在某些场景下,使用ReentrantLock更适合,功能更强大。 前两...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK