该昵称无法识别

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

Java源码剖析34讲学习笔记~5

synchronized 和 ReentrantLock 的实现原理是什么? 它们有什么区别?

Java 中每个对象都隐式包含一个 monitor(监视器) 对象

加锁的过程其实就是竞争 monitor 的过程

当线程进入字节码 monitorenter 指令之后

线程将持有 monitor 对象, 执行 monitorexit 时释放 monitor 对象

当其他线程没有拿到 monitor 对象时, 则需要阻塞等待获取该对象

synchronized

属于独占式悲观锁, 通过JVM隐式实现, 只允许同一时刻只有一个线程操作资源

ReentrantLock

是 Lock 的默认实现方式之一

是基于 AQS(Abstract Queued Synchronizer, 队列同步器) 实现的

默认是通过非公平锁实现的

它的内部有一个 state 的状态字段用于表示锁是否被占用

如果是 0 这表示锁未被占用, 此时线程就可以把 state 改为 1, 并成功获得锁

而其他未获得锁的线程只能去排队等待获取锁资源

1.6版本之后区别

1.5及之前synchronized性能远远低于ReentrantLock, 1.6之后则性能略低于ReentrantLock

1.6及之后区别如下:

  • synchronized 是 JVM 隐式实现的, 而 ReentrantLock 是 Java 语言提供的 API
  • ReentrantLock 可设置为公平锁, 而 synchronized 却不行
  • ReentrantLock 只能修饰代码块, 而 synchronized 可以用于修饰方法, 修饰代码块等
  • ReentrantLock 需要手动加锁和释放锁, 如果忘记释放锁, 则会造成资源被永久占用, 而 synchronized 无需手动释放锁
  • ReentrantLock 可以知道是否成功获得了锁, 而 synchronized 却不行

考点

  • ReentrantLock 的具体实现细节是什么?

    • 构造函数
    /**
     * 公平锁: 线程需要按照请求的顺序来获得锁
     * 非公平锁: 允许"插队"的情况存在(默认实现)
     * 插队: 指线程在发送请求的同时该锁的状态恰好变成了可用
     * - 那么此线程就可以跳过队列中所有排队的线程直接拥有该锁
     */
    public ReentrantLock() {
        sync = new NonfairSync(); // 非公平锁
    }
    
    public ReentrantLock(boolean fair){
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    • NonfairSync lock()
    final void lock() {
        if(compareAndSetState(0, 1))
            // 将当前线程设置为此锁的持有者
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    
    • FairSync lock()
    final void lock() {
        acquire(1);
    }
    
    • acquire()
    public final void acquire(int arg){
        // 尝试获取锁, 如果获取锁失败, 则把它加入到阻塞队列中
        if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    • tryAcquire(int acquires)
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 公平锁比非公平锁多了一行代码 !hasQueuedPredecessors()
            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;
    }
    
    • acquireQueued(final Node node, int arg)
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true; // 获取锁是否成功的状态标识
        try {
            boolean interrupted = false; // 线程是否被中断
            for (;;) {
                final Node p = node.predecessor(); // 获取前一个节点(前驱节点)
                if (p == head && tryAcquire(arg)) { // 当前节点为头节点的下一个节点时, 有权尝试获取锁
                    setHead(node); // 获取成功, 将当前节点设置为 head 节点
                    p.next = null; // help GC 原 head 节点出队, 等待被 GC
                    failed = false; // 获取成功
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) && // 判断获取锁失败后是否可以挂起
                    parkAndCheckInterrupt())
                    interrupted = true; // 线程若中断, 返回 true
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    • shouldParkAfterFailedAcquire(Node pred, Node node)
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus; // 获得前驱节点的状态
        if (ws == Node.SIGNAL) // 前驱节点的状态为 SIGNAL, 当前线程可以被挂起 (阻塞)
            return true;
        if (ws > 0) {
            do { // 若前驱节点状态为 CANCELLED, 那就一直往前找, 直到一个正常等待的状态为止
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node; // 并将当前节点排在它后边
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 把前驱节点的状态修改为 SIGNAL
        }
        return false;
    }
    
    • unlock()
    public void unlock(){
        sync.release(1);
    }
    
    public final boolean release(int arg){
        // 尝试释放锁
        if(tryRelease(arg)){
            // 释放成功
            Node h = head;
            if(h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    • tryRelease(int releases)
    // 尝试释放当前线程占有的锁
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases; // 释放锁后的状态, 0表示是否锁成功
        if (Thread.currentThread() != getExclusiveOwnerThread()) // 如果拥有锁的线程不熟当前线程的话抛出异常
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) { // 锁被成功释放
            free = true;
            setExclusiveOwnerThread(null); // 清空独占线程
        }
        setState(c); // 更新 state 值, 0表示为释放锁成功
        return free;
    }
    

    ![在这里插入图片描述]( https://img-blog.csdnimg.cn/20200715003507865.jpg?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NKRzc1Mzk1MQ==,size_16,color_FFFFFF,t_70#pic_center)

  • JDK1.6 时锁做了哪些优化?

    • 自适应自旋锁
      • 自旋的时间不再是固定的时间, 而是通过同一个锁对象自旋等待成功获取锁的几率来调整
    • 锁升级(锁膨胀)
      • 偏向锁
        • 指在无竞争的情况下设置的一种锁状态
        • 会偏向于第一个获取它的线程
        • 当锁对象第一次被获取到之后, 会在此对象头中设置标识为"01"
        • 表示偏向锁的模式, 并且在对象头中记录此线程的ID
        • 偏向锁可以提高带有同步但无竞争的程序性能
        • 但如果在多数锁总会被不同的现场访问时, 偏向锁模式就比较多余了
        • 此时可以通过 -XX:-UseBiasedLocking 来禁用偏向锁以提高性能
      • 轻量级锁
        • 是通过比较并交换 (CAS, Compare and Swap) 来实现的
        • 它对比的是线程和对象的 Mark Word (对象头中的一个区域)
        • 如果更新成功则表示当前线程成功拥有此锁
        • 如果失败, 虚拟机会先检查对象的 Mark Word 是否指向当前线程的栈帧
        • 如果是, 则说明当前线程已经拥有此锁, 否则, 则说明此锁已经被其他线程占用了
        • 当有两个以上的线程争抢此锁时, 轻量级锁就会膨胀为重量级锁
      • 重量级锁
        • 通过操作系统的互斥量(mutex lock) 来实现的
        • 这种实现方式需要在用户态和核心态之间做转换
        • 有很大的性能消耗, 这种传统实现锁的方式被称之为重量锁
posted on 2020-07-15 00:36  Java全栈路线  阅读(197)  评论(0)    收藏  举报