栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

synchronized和ReentrantLock的实现原理与区别?一篇文章就够了

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

synchronized和ReentrantLock的实现原理与区别?一篇文章就够了

文章目录

前言一、synchronized

1.1对象头1.2同步方法1.3同步代码块 二、ReentrantLock

2.1ReentrantLock概述2.2ReentrantLock执行流程 三、synchronized和Lock的区别总结


前言

如果某一个资源被多个线程共享,为了避免因为资源抢占导致资源数据错乱,我们需要对线程进行同步,那么synchronized和ReentrantLock就是实现线程同步的两个重要内容,可以说在并发控制中是必不可少的部分。本章就来介绍synchronized和ReentrantLock的底层原理。


一、synchronized 1.1对象头

在理解synchronized实现原理之前先了解一下Java的对象头和Monitor,在JVM中,对象是分成三部分存在的:对象头、实例数据、对齐填充。


对象头是我们需要关注的重点,它是synchronized实现锁的基础,因为synchronized申请锁、上锁、释放锁都与对象头有关。

关键:synchronized 基于进入和退出监视器对象(monitor)来实现方法同步和代码块同步,每个对象都存在着一个monitor与之关联,monitor对象存在于每个Java对象的对象头中(存储的指针的指向)


1.2同步方法

使用 ACC_SYNCHRonIZED 这标志告诉JVM这是一个同步方法

当方法调用时,调用指令将会检查方法的ACC_SYNCHRonIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(monitorenter指令,锁的计数器加1),然后再执行方法,退出方法时释放monitor(monitorexit指令,计数器-1)

反编译后的同步方法字节码文件


1.3同步代码块

同步块是由monitorenter指令进入,然后monitorexit释放锁,在执行monitorenter之前需要尝试获取锁,如果获取到锁,那么就把锁的计数器加1。当执行monitorexit指令时,锁的计数器也会减1。当获取锁失败时会被阻塞,一直等待锁被释放。

反编译后的同步代码块字节码文件

常情况下只会执行第一个monitorexit释放锁,然后返回。而如果在执行中发生了异常,第二个monitorexit就起作用了,它是由编译器自动生成的,在发生异常时处理异常然后释放掉锁。


二、ReentrantLock 2.1ReentrantLock概述

ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义

ReentrantLock 主要利用 CAS+AQS(抽象的队列式的同步器) 来实现,默认是非公平锁,也可以指定为公平锁。

public ReentrantLock() {
    sync = new NonfairSync(); //默认,非公平
}
 
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync(); //根据参数创建
}

AQS是用CLH队列锁(CLH同步队列是一个 FIFO双向队列,AQS依赖它来完成同步状态的管理)

用 volatile 修饰共享变量 state,线程通过 CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。


2.2ReentrantLock执行流程

ReentrantLock先通过CAS尝试获取锁
1.如果此时锁没有被占用,通过CAS获得锁。
2.如果此时锁已经被占用,该线程加入AQS队列并wait()
3.锁被释放的时候,挂在队首的线程就会被notify(),然后继续CAS尝试获取锁,此时:
非公平锁:如果有其他线程尝试lock(),有可能被其他刚好申请锁的线程抢占。
公平锁:只有在队列头的线程才可以获取锁,新来的线程只能插入到队尾。

lock()
如果成功通过CAS修改了state,指定当前线程为该锁的独占线程,标志自己成功获取锁。如果CAS失败的话,调用acquire();

    final void lock() { //非公平锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    final void lock() { //公平锁
            acquire(1);
    }

acquire():
首先,调用tryAcquire(),会尝试再次通过CAS修改state为1,如果失败而且发现锁是被当前线程占用的,就执行重入(state++);
如果锁是被其他线程占有,那么当前线程执行tryAcquire返回失败,并且执行addWaiter()进入等待队列,并挂起自己interrupt()。

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

unlock()
释放时候,state- -,通过state==0判断锁是否完全被释放。成功释放锁的话,唤起一个被挂起的线程

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;
}
//尝试释放锁
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;
}

总结:
1.每一个ReentrantLock自身维护一个AQS队列记录申请锁的线程信息。
2.通过大量CAS保证多个线程竞争锁的时候的并发安全。
3.可重入的功能是通过维护state变量来记录重入次数实现的。
4.公平锁需要维护队列,通过AQS队列的先后顺序获取锁,缺点是会造成大量线程上下文切换。
5.非公平锁可以直接抢占,所以效率更高。


三、synchronized和Lock的区别

1.来源及用法
synchronized:JAVA的一个内置关键字,托管给JVM执行;在需要同步的对象中加入此控制,可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象,加锁解锁的过程是隐式的。

Lock :是一个接口,是JAVA写的控制锁的代码,一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出

2.异常是否释放锁
synchronized:发生异常时候会自动释放占有的锁,因此不会出现死锁

Lock:发生异常时不会主动释放占有的锁,必须手动unlock来释放锁,所以一般会在finally块中写unlock()以防死锁。

3.锁特点
synchronized:可重入、不可中断、非公平。

Lock:可重入、等待时可中断、可判断、可公平可非公平。

4.是否阻塞
synchronized:获取不到锁只能一直阻塞,假设A线程获得锁,B线程等待,如果A线程阻塞,B线程会一直等待下去。

Lock:lock有多个锁获取的方法,可以尝试获得锁,如果尝试获取不到锁,线程可以不用一直等待就结束。

5.锁性能
synchronized:Java1.5中是性能低效的,因为这是一个重量级操作。Java1.6进行了很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等。
Lock:可以提高多个线程进行读写操作的效率。(可以通过readwritelock实现读写分离)

如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。


总结

synchronized和ReentrantLock的实现原理与区别是经常会被问到的知识点,都是并发编程中不可或缺的一部分。synchronized 基于进入和退出监视器对象(monitor)来实现方法同步和代码块同步,理解这一点非常关键。同时在ReentrantLock实现原理中再一次提到了CAS的概念,其重要性不言而喻。理解完两者的底层原理,回头去看Lock和synchronized的区别又会有一个新的认识,并且能够记忆深刻。


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/731500.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号