我是靠谱客的博主 英俊毛豆,最近开发中收集的这篇文章主要介绍ReentrantLock和synchronized两种锁定机制的对比,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

多线程和并发性

synchronized 不能中断锁

ReentrantLock 还多了 锁投票,定时锁等候和中断锁等候,是可重入锁

在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;

Synchronized锁原理

属于JVM层面的

分为:方法同步和代码块同步

比如:

//下列两个方法有什么区别 public synchronized void method1(){}//方法同步 public void method2(){//代码块同步 synchronized (obj){} }

方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符

代码块同步原理:锁的获取和释放分别是monitorenter和monitorexit指令,该锁在实现上分为了偏向锁、轻量级锁和重量级锁

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁

锁可以升级但不能降级

分别介绍四种锁:偏向锁:Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入,会在对象头和栈帧中的锁记录里存储锁偏向的线程 ID

锁存在Java对象头里。如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头

自旋锁、锁消除、锁粗化

ReentrantLock锁原理

属于api层面

**这里涉及到公平锁(FIFO)和非公平锁(运气)。

synchronized是非公平锁,ReentranLock默认也是非公平,可以通过构造方法改成公平锁(NonfairSync)。

AQS

维护了一个队列

AQS内部会保存一个状态变量state,通过CAS修改该变量的值,修改成功的线程表示获取到该锁,没有修改成功,或者发现状态state已经是加锁状态,则通过一个Waiter对象封装线程,添加到等待队列中,并挂起等待被唤醒

cas笔记cas无锁算法原理分析.note

线程使用ReentrantLock获取锁分为两个阶段,第一个阶段是初次竞争,第二个阶段是基于CHL队列的竞争。在初次竞争的时候是否考虑队列节点直接区分出了公平锁和非公平锁。在基于CHL队列的锁竞争中,依靠CAS操作保证原子操作,依靠LockSupport来做线程的挂起和唤醒,使用队列来保证并发执行变成了串行执行,从而消除了并发所带来的问题。总体来说,ReentrantLock是一个比较轻量级的锁,而且使用面向对象的思想去实现了锁的功能,比原来的synchronized关键字更加好理解。

AbstractQuenedSynchronizer是AQS的全程

看一下lock()实际的方法

   final void lock() {

       //尝试获取锁

       if (compareAndSetState(0, 1))

           setExclusiveOwnerThread(Thread.currentThread());

       else

           //获取失败,调用AQS的acquire(int arg)方法

           acquire(1);

   }

首先会第一次尝试快速获取锁,如果获取失败,则调用acquire(int arg)方法,该方法定义在AQS中,如下:

   public final void acquire(int arg) {

       if (!tryAcquire(arg) &&

               acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

           selfInterrupt();

   }

各个方法定义如下:

  1. tryAcquire:去尝试获取锁,获取成功则设置锁状态并返回true,否则返回false。该方法自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。
  2. addWaiter:如果tryAcquire返回FALSE(获取同步状态失败),则调用该方法将当前线程加入到CLH同步队列尾部。
  3. acquireQueued:当前线程会根据公平性原则来进行阻塞等待(自旋),直到获取锁为止;并且返回当前线程在等待过程中有没有中断过。
  4. selfInterrupt:产生一个中断。

这个方法首先调用tryAcquire(int arg)方法,在AQS中讲述过,tryAcquire(int arg)需要自定义同步组件提供实现,非公平锁实现如下:

   protected final boolean tryAcquire(int acquires) {

       return nonfairTryAcquire(acquires);

   }

   final boolean nonfairTryAcquire(int acquires) {

       //当前线程

       final Thread current = Thread.currentThread();

       //获取同步状态

       int c = getState();

       //state == 0,表示没有该锁处于空闲状态

       if (c == 0) {

           //获取锁成功,设置为当前线程所有

           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");

           setState(nextc);

           return true;

       }

       return false;

   }

该方法主要逻辑:首先判断同步状态state == 0 ?,如果是表示该锁还没有被线程持有,直接通过CAS获取同步状态,如果成功返回true。如果state != 0,则判断当前线程是否为获取锁的线程,如果是则获取锁,成功返回true。成功获取锁的线程再次获取锁,这是增加了同步状态state。

整个AQS同步组件、Atomic原子类操作等等都是以CAS实现的

区别

Lock锁可以精确的唤醒一个线程,syn不行,Condition绑定多个条件,这就是好处

最后

以上就是英俊毛豆为你收集整理的ReentrantLock和synchronized两种锁定机制的对比的全部内容,希望文章能够帮你解决ReentrantLock和synchronized两种锁定机制的对比所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(60)

评论列表共有 0 条评论

立即
投稿
返回
顶部